Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
c6d306f
feat: impl pipeline multi-threading
McPatate Jul 14, 2026
22033b3
feat: introduce parallelplan strategy
McPatate Jul 17, 2026
61a82cd
refactor: remove unused `add_special_tokens`
McPatate Jul 17, 2026
f866644
chore: cleanup pipeline bindings
McPatate Jul 20, 2026
addabaa
chore: cleanup pipeline benchmark
McPatate Jul 20, 2026
3765e0a
fix: update encode API
McPatate Jul 20, 2026
1c0f9e5
feat: add special tokens & normalization chunking
McPatate Jul 21, 2026
b644aba
chore: cleanup
McPatate Jul 21, 2026
8581dfa
chore: more cleanup
McPatate Jul 22, 2026
1cffe7f
wip / ai: scratch pool
SBrandeis Jul 20, 2026
7556ac7
scratch pool iteration
SBrandeis Jul 21, 2026
b5ef46f
implement word cache
SBrandeis Jul 21, 2026
8000719
lint
SBrandeis Jul 21, 2026
ee332b0
change defaults + max length + capacity bump
SBrandeis Jul 21, 2026
75f796c
more sensible values
SBrandeis Jul 21, 2026
d42d866
rewrite without hashmap
SBrandeis Jul 21, 2026
4106c3f
iteration
SBrandeis Jul 21, 2026
96a1778
iteration
SBrandeis Jul 21, 2026
40d37b3
fmt
SBrandeis Jul 21, 2026
6b26583
review comments
SBrandeis Jul 22, 2026
8aec88b
chore: harmonize naming for segments and parallel splitting
McPatate Jul 23, 2026
e52925c
chore: docs conciseness attempt
McPatate Jul 23, 2026
2f0e387
perf: reduce allocations + avoid false sharing
McPatate Jul 23, 2026
502347f
refactor: remove channel because of immense contention
McPatate Jul 23, 2026
8596486
chore: trim docs
McPatate Jul 23, 2026
c87102d
draft first commit
ArthurZucker Jul 24, 2026
dfde37a
nits
ArthurZucker Jul 24, 2026
1436856
proper bpe only benches
ArthurZucker Jul 24, 2026
6759ed1
proper features
ArthurZucker Jul 24, 2026
3c532af
update
ArthurZucker Jul 24, 2026
43ac1ab
update what we balckbox
ArthurZucker Jul 24, 2026
852c82a
chore: address comments
McPatate Jul 24, 2026
062c6f8
comments
ArthurZucker Jul 27, 2026
cb4123a
commetn
ArthurZucker Jul 27, 2026
311eec7
first drafts
ArthurZucker Jul 27, 2026
330972f
fix glue
ArthurZucker Jul 27, 2026
3449330
draft table builds
ArthurZucker Jul 28, 2026
5a9cd45
start mphf draft
ArthurZucker Jul 28, 2026
8e3347b
impl get
ArthurZucker Jul 28, 2026
f11a5f9
fix mphf
ArthurZucker Jul 28, 2026
e0a073d
nits
ArthurZucker Jul 28, 2026
8c667db
fixes
ArthurZucker Jul 28, 2026
6ffbfba
slowly but surely
ArthurZucker Jul 28, 2026
346e99e
fixes
ArthurZucker Jul 28, 2026
2227c40
table tests pass
ArthurZucker Jul 28, 2026
12690e9
create top merges
ArthurZucker Jul 28, 2026
3474720
update
ArthurZucker Jul 28, 2026
d66b177
up
ArthurZucker Jul 28, 2026
14c0a2f
progress in the conversion table build
ArthurZucker Jul 29, 2026
b1f44bc
update
ArthurZucker Jul 29, 2026
519bbcc
fixes
ArthurZucker Jul 29, 2026
9f23a46
dman
ArthurZucker Jul 29, 2026
795131c
...
ArthurZucker Jul 29, 2026
07efea6
eager flag was missing
ArthurZucker Jul 29, 2026
4066919
update
ArthurZucker Jul 29, 2026
cda77f8
Reuse the encode scratch across calls
SBrandeis Jul 29, 2026
cb8ff8b
comments
SBrandeis Jul 29, 2026
33d0001
design a manual example
ArthurZucker Jul 30, 2026
836bcfe
with / without thief
ArthurZucker Jul 30, 2026
1b498e3
remove some shit
ArthurZucker Jul 30, 2026
b432729
more simplifications
ArthurZucker Jul 30, 2026
b3fcd70
single test move it
ArthurZucker Jul 30, 2026
d542edc
draft engine
ArthurZucker Jul 30, 2026
1965a6c
remove complication
ArthurZucker Jul 30, 2026
7ae223c
remove one flag that was not worth it
ArthurZucker Jul 30, 2026
8b3d2d7
checkpoint
SBrandeis Jul 30, 2026
3b87e24
vectorized sidecar search
SBrandeis Jul 30, 2026
ec79fc7
use SWAR
SBrandeis Jul 30, 2026
5a9f2e7
experiment: skip vocab lookup
SBrandeis Jul 30, 2026
320f32d
perf(wordpiece): serve repeated words from the WordCache
SBrandeis Jul 30, 2026
2ce3933
module docs
SBrandeis Jul 30, 2026
58a4b96
revert experiment
SBrandeis Jul 30, 2026
85e5e1a
refactor, rename, recyce
SBrandeis Jul 30, 2026
26959f2
cache ignore merges
SBrandeis Jul 30, 2026
2e4932c
cleanup
SBrandeis Jul 30, 2026
bdf2e96
under-engineer
SBrandeis Jul 30, 2026
ddcedf0
comments
SBrandeis Jul 30, 2026
cc76dfc
comments
SBrandeis Jul 30, 2026
1f9ba07
prefer evict as a keyword
SBrandeis Jul 30, 2026
ec119a6
current status
ArthurZucker Jul 31, 2026
3a7aa8f
monomorphize for perfs
ArthurZucker Jul 31, 2026
c3aad7a
fixes
ArthurZucker Jul 31, 2026
abae229
rename
ArthurZucker Aug 3, 2026
8a616f3
start drafting the two tier merge
ArthurZucker Aug 3, 2026
f0e6031
todo is getting fixed
ArthurZucker Aug 3, 2026
5df6783
nit
ArthurZucker Aug 3, 2026
678b9f6
SparseFold for table!
ArthurZucker Aug 3, 2026
27ab17b
move stuff around
ArthurZucker Aug 3, 2026
4b3f37b
multipass bpe
ArthurZucker Aug 3, 2026
d3a8b02
scratches
ArthurZucker Aug 3, 2026
d0d6e41
isolate pipeline bpe
ArthurZucker Aug 3, 2026
ba35ffc
remove unused mergemap
ArthurZucker Aug 3, 2026
99e0846
u16 is faster
ArthurZucker Aug 3, 2026
3603372
add convert.rs
ArthurZucker Aug 3, 2026
563ab9b
update
ArthurZucker Aug 3, 2026
e474cdb
less branches
ArthurZucker Aug 3, 2026
6fa4281
update
ArthurZucker Aug 3, 2026
23542bd
it was not worth it
ArthurZucker Aug 3, 2026
abbb762
finally
ArthurZucker Aug 3, 2026
088e329
nits, renaming and moving files here and there
ArthurZucker Aug 3, 2026
bd95276
renaming
ArthurZucker Aug 3, 2026
06dbb96
fix more of the renaming
ArthurZucker Aug 3, 2026
1fee23b
pipeline vs legacy
ArthurZucker Aug 3, 2026
93bd4ac
Merge remote-tracking branch 'origin/feat/train_encode_split' into pe…
ArthurZucker Aug 3, 2026
703e37f
first fix for the gate
ArthurZucker Aug 3, 2026
8523274
update the local bench
ArthurZucker Aug 3, 2026
0bde560
up
ArthurZucker Aug 3, 2026
11e1786
bit better benches
ArthurZucker Aug 3, 2026
1ce1f6c
update
ArthurZucker Aug 3, 2026
a6bea80
revert unrelated changes
ArthurZucker Aug 3, 2026
47098dc
more reverts
ArthurZucker Aug 3, 2026
a42edc9
more reversion!
ArthurZucker Aug 3, 2026
9dc69e3
cleanup
ArthurZucker Aug 3, 2026
490e3f0
nits
ArthurZucker Aug 3, 2026
4d2b037
more clippy
ArthurZucker Aug 3, 2026
2624c43
rewrite + document multipass
SBrandeis Aug 3, 2026
db1798d
rename
SBrandeis Aug 3, 2026
7598fb2
perf: save a lookup when there is no merge
SBrandeis Aug 3, 2026
ff4d576
perf: compiler gotchas
SBrandeis Aug 3, 2026
e9fe3ca
Idiomacy:
SBrandeis Aug 3, 2026
d5b1aea
poc(encode): the target encode path -- bitsplit + batched model + fus…
ArthurZucker Aug 4, 2026
430b8a4
fix(poc): keep the FSM where bitsplit has no SIMD builder
ArthurZucker Aug 4, 2026
8f0dedf
feat(bitsplit): SSSE3 block builder, so x86 gets the bitstream split too
ArthurZucker Aug 4, 2026
86bd5fa
bench(fixture): one long input per fixture instead of 10 kB chunks
ArthurZucker Aug 4, 2026
8a74a2d
Merge branch 'perf/bpe-merge-review' into poc/target-encode
ArthurZucker Aug 4, 2026
6193e33
Merge branch 'feat/multi_threaded_pipeline' into poc/target-encode
ArthurZucker Aug 4, 2026
68221b5
bench(par_check): guard that the parallel encode stays parallel
ArthurZucker Aug 4, 2026
688c0d0
bench(ab_giga): the gigatoken A/B harness, and route encode_one throu…
ArthurZucker Aug 4, 2026
5ea9602
bench(ab_giga_mt): multi-thread arm of the gigatoken A/B
ArthurZucker Aug 4, 2026
c7ae7f4
bench(ab_giga): reuse the output buffer, matching gigatoken's harness
ArthurZucker Aug 4, 2026
106327e
fix(pipeline): don't stride an input that has no cut points
ArthurZucker Aug 4, 2026
2b4da60
perf(bpe): gate the merge engine on the first content byte, not the d…
ArthurZucker Aug 6, 2026
eabddcd
Merge feat/train_encode_split into poc/target-encode
ArthurZucker Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bindings/python/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bindings/python/py_src/tokenizers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ class SplitDelimiterBehavior(Enum):

from .tokenizers import (
AddedToken,
EncodeHandle,
Encoding,
NormalizedString,
PipelineTokenizer,
PreTokenizedString,
Regex,
Token,
Expand Down
5 changes: 5 additions & 0 deletions bindings/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod processors;
mod token;
mod tokenizer;
mod trainers;
mod pipeline;
mod utils;

use pyo3::prelude::*;
Expand Down Expand Up @@ -60,6 +61,10 @@ pub mod tokenizers {
#[pymodule_export]
pub use super::tokenizer::PyTokenizer;
#[pymodule_export]
pub use super::pipeline::PyPipelineTokenizer;
#[pymodule_export]
pub use super::pipeline::PyEncodeHandle;
#[pymodule_export]
pub use super::utils::PyNormalizedString;
#[pymodule_export]
pub use super::utils::PyPreTokenizedString;
Expand Down
172 changes: 172 additions & 0 deletions bindings/python/src/pipeline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//! Pipeline python bindings

use std::sync::Mutex;

use pyo3::exceptions;
use pyo3::prelude::*;
use pyo3::types::PyString;

use tk::pipeline::{EncodeHandle, Inputs, IntoInputs, PipelineToken, PipelineTokenizer};

use crate::error::ToPyResult;
use crate::tokenizer::PyTokenizer;

fn ids(tokens: Vec<PipelineToken>) -> Vec<u32> {
tokens.iter().map(|t| t.id).collect()
}

struct PyStrView {
ptr: *const u8,
len: usize,
}

/// Keep alive mechanism for zero-copy access to utf8 python strings
struct PyStrBatch {
/// the keep alive ref
_owners: Vec<Py<PyString>>,
/// (ptr, len)
views: Vec<PyStrView>,
}

// SAFETY: we only ever read from the underlying buffers,
// and the lifetime of the buffers is guaranteed by storing
// the Py<PyString> in _owners, which is kept alive as long as
// a reference is held, because Python refcount.
unsafe impl Send for PyStrBatch {}
unsafe impl Sync for PyStrBatch {}

impl PyStrBatch {
fn new(strings: Vec<Bound<'_, PyString>>) -> PyResult<Self> {
let mut owners = Vec::with_capacity(strings.len());
let mut views = Vec::with_capacity(strings.len());
for s in strings {
let view = s.to_str()?;
views.push(PyStrView {
ptr: view.as_ptr(),
len: view.len(),
});
owners.push(s.unbind());
}
Ok(Self {
_owners: owners,
views,
})
}
}

impl Inputs for PyStrBatch {
fn len(&self) -> usize {
self.views.len()
}

fn get(&self, i: usize) -> &str {
let (ptr, len) = (self.views[i].ptr, self.views[i].len);
// SAFETY: ptr is kept alive by PyStrBatch::_owners, and the underlying buffer is immutable UTF-8
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(ptr, len)) }
}
}

impl IntoInputs for PyStrBatch {
type Inputs = PyStrBatch;
fn into_inputs(self) -> PyStrBatch {
self
}
}

#[pyclass(module = "tokenizers", name = "PipelineTokenizer")]
pub struct PyPipelineTokenizer {
pipeline: PipelineTokenizer,
}

#[pymethods]
impl PyPipelineTokenizer {
#[staticmethod]
fn from_tokenizer(tokenizer: &PyTokenizer) -> PyResult<Self> {
let json = {
let guard = tokenizer.read_inner()?;
serde_json::to_string(&*guard)
.map_err(|e| exceptions::PyException::new_err(format!("{e}")))?
};
let tok: tk::Tokenizer = serde_json::from_str(&json)
.map_err(|e| exceptions::PyException::new_err(format!("{e}")))?;
let pipeline = PyResult::from(ToPyResult(PipelineTokenizer::try_from(&tok)))?;
Ok(Self { pipeline })
}

#[staticmethod]
fn from_file(path: &str) -> PyResult<Self> {
let tok = PyResult::from(ToPyResult(tk::Tokenizer::from_file(path)))?;
let pipeline = PyResult::from(ToPyResult(PipelineTokenizer::try_from(&tok)))?;
Ok(Self { pipeline })
}

/// Encode a batch of `str`, returning an [`PyEncodeHandle`] as soon as possible:
/// pool workers encode in the background while you hold the job, `wait()`
/// for everything (returned in input order), or iterate `(index, ids)` as
/// each input completes. Zero-copy: the job reads the Python strings' UTF-8
/// buffers in place.
fn encode_batch(
&self,
py: Python<'_>,
input: Vec<Bound<'_, PyString>>,
) -> PyResult<PyEncodeHandle> {
let batch = PyStrBatch::new(input)?;
// Below the cost gate the job is computed inline — release the GIL.
let job = py.detach(|| self.pipeline.encode(batch));
Ok(PyEncodeHandle {
job: Mutex::new(Some(job)),
})
}
}

/// An in-flight (or completed) batch encode. `wait()` blocks for all inputs and
/// returns their ids lists in input order; iterating yields `(index, ids)` for
/// each input as it completes (completion order, not input order — use `index`
/// to place it). The consuming thread *assists* the pool while it waits (all
/// with the GIL released). Dropping the job cancels unclaimed work.
#[pyclass(module = "tokenizers", name = "EncodeHandle")]
pub struct PyEncodeHandle {
job: Mutex<Option<EncodeHandle>>,
}

impl PyEncodeHandle {
fn take(&self) -> PyResult<EncodeHandle> {
self.job
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
.ok_or_else(|| exceptions::PyException::new_err("EncodeHandle already consumed"))
}
}

#[pymethods]
impl PyEncodeHandle {
/// Block until every input is encoded, returning ids lists in input order
fn wait(&self, py: Python<'_>) -> PyResult<Vec<Vec<u32>>> {
let job = self.take()?;
let res = py.detach(|| job.wait_for_completion());
let out = PyResult::from(ToPyResult(res))?;
Ok(out.into_iter().map(ids).collect())
}

fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}

/// Yield `(input index, ids)` for each input as it finishes — completion
/// order, not input order. `index` is the position in the batch passed to
/// `encode_batch` (always `0` for a single input).
fn __next__(&self, py: Python<'_>) -> PyResult<Option<(usize, Vec<u32>)>> {
let next = py.detach(|| {
self.job
.lock()
.unwrap_or_else(|e| e.into_inner())
.as_mut()
.and_then(|j| j.next())
});
match next {
None => Ok(None),
Some((seq, res)) => Ok(Some((seq, ids(PyResult::from(ToPyResult(res))?)))),
}
}
}
Loading
Loading