gigatoken 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +3016 -0
- data/Cargo.toml +135 -0
- data/LICENSE +21 -0
- data/README.md +141 -0
- data/exe/gigatoken +9 -0
- data/ext/gigatoken/Cargo.toml +24 -0
- data/ext/gigatoken/extconf.rb +19 -0
- data/ext/gigatoken/src/error.rs +20 -0
- data/ext/gigatoken/src/gvl.rs +122 -0
- data/ext/gigatoken/src/lib.rs +50 -0
- data/ext/gigatoken/src/sentencepiece.rs +205 -0
- data/ext/gigatoken/src/sources.rs +207 -0
- data/ext/gigatoken/src/tokenizer.rs +571 -0
- data/lib/gigatoken/cli/bench.rb +64 -0
- data/lib/gigatoken/cli/support.rb +132 -0
- data/lib/gigatoken/cli/validate.rb +55 -0
- data/lib/gigatoken/cli.rb +18 -0
- data/lib/gigatoken/hub.rb +242 -0
- data/lib/gigatoken/packed_result.rb +48 -0
- data/lib/gigatoken/tokenizer.rb +117 -0
- data/lib/gigatoken/version.rb +5 -0
- data/lib/gigatoken.rb +29 -0
- data/rust-toolchain.toml +8 -0
- data/src/batch.rs +1808 -0
- data/src/bindings/bridge.rs +396 -0
- data/src/bindings/hub.rs +42 -0
- data/src/bindings/matcher.rs +114 -0
- data/src/bindings/mod.rs +14 -0
- data/src/bindings/padding.rs +177 -0
- data/src/bindings/pretokenize.rs +53 -0
- data/src/bindings/sources.rs +273 -0
- data/src/bindings/train.rs +125 -0
- data/src/bpe/mod.rs +1217 -0
- data/src/bpe/pretoken_cache.rs +495 -0
- data/src/bpe/sentencepiece.rs +1485 -0
- data/src/bpe/tiktoken.rs +2555 -0
- data/src/bpe_train.rs +351 -0
- data/src/input/decompress.rs +11 -0
- data/src/input/file_source.rs +514 -0
- data/src/input/jsonl.rs +94 -0
- data/src/input/mod.rs +333 -0
- data/src/input/parquet.rs +303 -0
- data/src/lib.rs +578 -0
- data/src/load_tokenizer/hf.rs +1036 -0
- data/src/load_tokenizer/hub.rs +344 -0
- data/src/load_tokenizer/mod.rs +3 -0
- data/src/load_tokenizer/tiktoken.rs +87 -0
- data/src/main.rs +95 -0
- data/src/pretokenize/fast/cl100k.rs +426 -0
- data/src/pretokenize/fast/cl100k_family.rs +891 -0
- data/src/pretokenize/fast/deepseek_v3.rs +605 -0
- data/src/pretokenize/fast/kimi.rs +281 -0
- data/src/pretokenize/fast/mask.rs +1486 -0
- data/src/pretokenize/fast/mod.rs +446 -0
- data/src/pretokenize/fast/nemotron.rs +138 -0
- data/src/pretokenize/fast/o200k.rs +347 -0
- data/src/pretokenize/fast/o200k_family.rs +1734 -0
- data/src/pretokenize/fast/olmo3.rs +505 -0
- data/src/pretokenize/fast/qwen2.rs +429 -0
- data/src/pretokenize/fast/qwen3_5.rs +541 -0
- data/src/pretokenize/fast/r50k.rs +1250 -0
- data/src/pretokenize/mod.rs +1079 -0
- data/src/pretokenize/options.rs +188 -0
- data/src/pretokenize/pretoken.rs +20 -0
- data/src/pretokenize/pretokenize_traits.rs +49 -0
- data/src/pretokenize/reference/avx512.rs +522 -0
- data/src/pretokenize/reference/combinator.rs +572 -0
- data/src/pretokenize/reference/mod.rs +28 -0
- data/src/pretokenize/reference/simd.rs +852 -0
- data/src/pretokenize/reference/state_machine.rs +365 -0
- data/src/pretokenize/unicode.rs +546 -0
- data/src/test_hub.rs +28 -0
- data/src/token.rs +42 -0
- metadata +161 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
use crate::pretokenize::Pretoken;
|
|
2
|
+
use crate::pretokenize::fast::{
|
|
3
|
+
FastCl100kPretokenizer, FastDeepSeekV3Pretokenizer, FastKimiPretokenizer,
|
|
4
|
+
FastNemotronPretokenizer, FastO200kPretokenizer, FastOlmo3Pretokenizer,
|
|
5
|
+
FastQwen2Pretokenizer, FastQwen35Pretokenizer, FastR50kPretokenizer,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/// Which pretokenization scheme (regex) a tokenizer uses.
|
|
9
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
10
|
+
pub enum PretokenizerType {
|
|
11
|
+
GPT2, // Also used by llama, also known as r50k
|
|
12
|
+
GPT4, // cl100k
|
|
13
|
+
Qwen2, // Slightly adapted from GPT4, also used by Qwen3
|
|
14
|
+
Qwen35, // Qwen2 with `[\p{L}\p{M}]+` letter runs, marks excluded from punct runs
|
|
15
|
+
Olmo3, // dolma2: Qwen2 scheme with cl100k's \p{N}{1,3}; used by Olmo 2/3
|
|
16
|
+
DeepSeekV3, // Sequence of three Splits (digits, CJK, main); used by DeepSeek V3/V3.1/V4
|
|
17
|
+
O200k, // o200k_base: case-structured letter runs; GPT-4o, gpt-oss
|
|
18
|
+
Nemotron, // o200k without contractions, single-digit \p{N}; nvidia Nemotron-3
|
|
19
|
+
Kimi, // o200k with [\p{Han}]+ runs and no `/` tail absorption; moonshotai Kimi-K2 line
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// The three Split regexes of the DeepSeek V3/V4 pre_tokenizer Sequence, as
|
|
23
|
+
/// they appear in tokenizer.json (the third contains literal CR/LF chars,
|
|
24
|
+
/// not `\r`/`\n` escapes).
|
|
25
|
+
const DEEPSEEK_V3_SPLIT_REGEXES: [&str; 3] = [
|
|
26
|
+
r"\p{N}{1,3}",
|
|
27
|
+
"[\u{4e00}-\u{9fa5}\u{3040}-\u{309f}\u{30a0}-\u{30ff}]+",
|
|
28
|
+
"[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~][A-Za-z]+|[^\r\n\\p{L}\\p{P}\\p{S}]?[\\p{L}\\p{M}]+| ?[\\p{P}\\p{S}]+[\r\n]*|\\s*[\r\n]+|\\s+(?!\\S)|\\s+",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
impl PretokenizerType {
|
|
32
|
+
/// Fast pretokenizer for this scheme.
|
|
33
|
+
///
|
|
34
|
+
/// The returned enum dispatches once per token; for hot loops over a
|
|
35
|
+
/// known scheme, use the concrete iterator types directly.
|
|
36
|
+
#[inline]
|
|
37
|
+
pub fn pretokenize<'a>(&self, bytes: &'a [u8]) -> FastPretokenizerDispatch<'a> {
|
|
38
|
+
match self {
|
|
39
|
+
PretokenizerType::GPT2 => {
|
|
40
|
+
FastPretokenizerDispatch::R50k(FastR50kPretokenizer::new(bytes))
|
|
41
|
+
}
|
|
42
|
+
PretokenizerType::GPT4 => {
|
|
43
|
+
FastPretokenizerDispatch::Cl100k(FastCl100kPretokenizer::new(bytes))
|
|
44
|
+
}
|
|
45
|
+
PretokenizerType::Qwen2 => {
|
|
46
|
+
FastPretokenizerDispatch::Qwen2(FastQwen2Pretokenizer::new(bytes))
|
|
47
|
+
}
|
|
48
|
+
PretokenizerType::Qwen35 => {
|
|
49
|
+
FastPretokenizerDispatch::Qwen35(FastQwen35Pretokenizer::new(bytes))
|
|
50
|
+
}
|
|
51
|
+
PretokenizerType::Olmo3 => {
|
|
52
|
+
FastPretokenizerDispatch::Olmo3(FastOlmo3Pretokenizer::new(bytes))
|
|
53
|
+
}
|
|
54
|
+
PretokenizerType::DeepSeekV3 => {
|
|
55
|
+
FastPretokenizerDispatch::DeepSeekV3(FastDeepSeekV3Pretokenizer::new(bytes))
|
|
56
|
+
}
|
|
57
|
+
PretokenizerType::O200k => {
|
|
58
|
+
FastPretokenizerDispatch::O200k(FastO200kPretokenizer::new(bytes))
|
|
59
|
+
}
|
|
60
|
+
PretokenizerType::Nemotron => {
|
|
61
|
+
FastPretokenizerDispatch::Nemotron(FastNemotronPretokenizer::new(bytes))
|
|
62
|
+
}
|
|
63
|
+
PretokenizerType::Kimi => {
|
|
64
|
+
FastPretokenizerDispatch::Kimi(FastKimiPretokenizer::new(bytes))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// The scheme named by a lowercase identifier, as used by loaders whose
|
|
70
|
+
/// source format carries no split regex (e.g. tiktoken-rank repos, whose
|
|
71
|
+
/// regex lives in remote code) and the Python bindings. Accepts each
|
|
72
|
+
/// variant's canonical name plus the common tiktoken aliases.
|
|
73
|
+
pub fn from_name(name: &str) -> Option<Self> {
|
|
74
|
+
Some(match name {
|
|
75
|
+
"gpt2" | "r50k" => PretokenizerType::GPT2,
|
|
76
|
+
"gpt4" | "cl100k" => PretokenizerType::GPT4,
|
|
77
|
+
"qwen2" => PretokenizerType::Qwen2,
|
|
78
|
+
"qwen35" => PretokenizerType::Qwen35,
|
|
79
|
+
"olmo3" => PretokenizerType::Olmo3,
|
|
80
|
+
"deepseek_v3" => PretokenizerType::DeepSeekV3,
|
|
81
|
+
"o200k" => PretokenizerType::O200k,
|
|
82
|
+
"nemotron" => PretokenizerType::Nemotron,
|
|
83
|
+
"kimi" => PretokenizerType::Kimi,
|
|
84
|
+
_ => return None,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Identify the scheme from the ordered list of `Split` regexes found in
|
|
89
|
+
/// a HuggingFace `tokenizer.json` pre_tokenizer. Returns `None` for
|
|
90
|
+
/// unknown patterns.
|
|
91
|
+
pub fn from_split_regexes(patterns: &[&str]) -> Option<Self> {
|
|
92
|
+
match patterns {
|
|
93
|
+
[p] => Self::from_split_regex(p),
|
|
94
|
+
_ if patterns == DEEPSEEK_V3_SPLIT_REGEXES => Some(PretokenizerType::DeepSeekV3),
|
|
95
|
+
_ => None,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Identify the scheme from the `Split` regex found in a HuggingFace
|
|
100
|
+
/// `tokenizer.json` pre_tokenizer. Returns `None` for unknown patterns.
|
|
101
|
+
pub fn from_split_regex(pattern: &str) -> Option<Self> {
|
|
102
|
+
match pattern {
|
|
103
|
+
r"'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+" => {
|
|
104
|
+
Some(PretokenizerType::GPT2)
|
|
105
|
+
}
|
|
106
|
+
r"'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}++|\p{N}{1,3}+| ?[^\s\p{L}\p{N}]++[\r\n]*+|\s++$|\s*[\r\n]|\s+(?!\S)|\s+"
|
|
107
|
+
| r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+" => {
|
|
108
|
+
Some(PretokenizerType::GPT4)
|
|
109
|
+
}
|
|
110
|
+
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+" => {
|
|
111
|
+
Some(PretokenizerType::Qwen2)
|
|
112
|
+
}
|
|
113
|
+
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?[\p{L}\p{M}]+|\p{N}| ?[^\s\p{L}\p{M}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+" => {
|
|
114
|
+
Some(PretokenizerType::Qwen35)
|
|
115
|
+
}
|
|
116
|
+
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+" => {
|
|
117
|
+
Some(PretokenizerType::Olmo3)
|
|
118
|
+
}
|
|
119
|
+
r"[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+(?i:'s|'t|'re|'ve|'m|'ll|'d)?|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*(?i:'s|'t|'re|'ve|'m|'ll|'d)?|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n/]*|\s*[\r\n]+|\s+(?!\S)|\s+" => {
|
|
120
|
+
Some(PretokenizerType::O200k)
|
|
121
|
+
}
|
|
122
|
+
r"[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n/]*|\s*[\r\n]+|\s+(?!\S)|\s+" => {
|
|
123
|
+
Some(PretokenizerType::Nemotron)
|
|
124
|
+
}
|
|
125
|
+
_ => None,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Runtime-selected fast pretokenizer; add a variant here when implementing
|
|
131
|
+
/// a new scheme under `fast`.
|
|
132
|
+
pub enum FastPretokenizerDispatch<'a> {
|
|
133
|
+
R50k(FastR50kPretokenizer<'a>),
|
|
134
|
+
Cl100k(FastCl100kPretokenizer<'a>),
|
|
135
|
+
Qwen2(FastQwen2Pretokenizer<'a>),
|
|
136
|
+
Qwen35(FastQwen35Pretokenizer<'a>),
|
|
137
|
+
Olmo3(FastOlmo3Pretokenizer<'a>),
|
|
138
|
+
DeepSeekV3(FastDeepSeekV3Pretokenizer<'a>),
|
|
139
|
+
O200k(FastO200kPretokenizer<'a>),
|
|
140
|
+
Nemotron(FastNemotronPretokenizer<'a>),
|
|
141
|
+
Kimi(FastKimiPretokenizer<'a>),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl<'a> Iterator for FastPretokenizerDispatch<'a> {
|
|
145
|
+
type Item = Pretoken<'a>;
|
|
146
|
+
|
|
147
|
+
#[inline]
|
|
148
|
+
fn next(&mut self) -> Option<Pretoken<'a>> {
|
|
149
|
+
match self {
|
|
150
|
+
FastPretokenizerDispatch::R50k(it) => it.next(),
|
|
151
|
+
FastPretokenizerDispatch::Cl100k(it) => it.next(),
|
|
152
|
+
FastPretokenizerDispatch::Qwen2(it) => it.next(),
|
|
153
|
+
FastPretokenizerDispatch::Qwen35(it) => it.next(),
|
|
154
|
+
FastPretokenizerDispatch::Olmo3(it) => it.next(),
|
|
155
|
+
FastPretokenizerDispatch::DeepSeekV3(it) => it.next(),
|
|
156
|
+
FastPretokenizerDispatch::O200k(it) => it.next(),
|
|
157
|
+
FastPretokenizerDispatch::Nemotron(it) => it.next(),
|
|
158
|
+
FastPretokenizerDispatch::Kimi(it) => it.next(),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// SAFETY: pure delegation to the concrete pretokenizers' (contract-upholding)
|
|
164
|
+
// fills; no entries are written here.
|
|
165
|
+
unsafe impl<'a> crate::pretokenize::PretokenSpans<'a> for FastPretokenizerDispatch<'a> {
|
|
166
|
+
/// One dispatch per chunk instead of one per pretoken, delegating to
|
|
167
|
+
/// the concrete pretokenizers' fused chunk fills.
|
|
168
|
+
#[inline]
|
|
169
|
+
fn fill_spans_keyed(
|
|
170
|
+
&mut self,
|
|
171
|
+
batch: &mut crate::pretokenize::SpanBatch<'a>,
|
|
172
|
+
prefetch: &impl Fn(u64),
|
|
173
|
+
) -> usize {
|
|
174
|
+
use crate::pretokenize::PretokenSpans;
|
|
175
|
+
match self {
|
|
176
|
+
FastPretokenizerDispatch::R50k(it) => it.fill_spans_keyed(batch, prefetch),
|
|
177
|
+
FastPretokenizerDispatch::Cl100k(it) => it.fill_spans_keyed(batch, prefetch),
|
|
178
|
+
FastPretokenizerDispatch::Qwen2(it) => it.fill_spans_keyed(batch, prefetch),
|
|
179
|
+
FastPretokenizerDispatch::Qwen35(it) => it.fill_spans_keyed(batch, prefetch),
|
|
180
|
+
FastPretokenizerDispatch::Olmo3(it) => it.fill_spans_keyed(batch, prefetch),
|
|
181
|
+
FastPretokenizerDispatch::DeepSeekV3(it) => it.fill_spans_keyed(batch, prefetch),
|
|
182
|
+
FastPretokenizerDispatch::O200k(it) => it.fill_spans_keyed(batch, prefetch),
|
|
183
|
+
FastPretokenizerDispatch::Nemotron(it) => it.fill_spans_keyed(batch, prefetch),
|
|
184
|
+
FastPretokenizerDispatch::Kimi(it) => it.fill_spans_keyed(batch, prefetch),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//! Once we have a document, we can pretokenize it (potentially in parallel)
|
|
2
|
+
|
|
3
|
+
use std::ops::Deref;
|
|
4
|
+
|
|
5
|
+
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
|
6
|
+
pub struct Pretoken<'a>(pub &'a [u8]);
|
|
7
|
+
|
|
8
|
+
impl AsRef<[u8]> for Pretoken<'_> {
|
|
9
|
+
fn as_ref(&self) -> &[u8] {
|
|
10
|
+
self.0
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl<'a> Deref for Pretoken<'a> {
|
|
15
|
+
type Target = &'a [u8];
|
|
16
|
+
|
|
17
|
+
fn deref(&self) -> &Self::Target {
|
|
18
|
+
&self.0
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use crate::pretokenize::pretoken::Pretoken;
|
|
2
|
+
use rayon::prelude::*;
|
|
3
|
+
use std::{
|
|
4
|
+
collections::HashMap,
|
|
5
|
+
hash::{BuildHasher, Hash},
|
|
6
|
+
ops::AddAssign,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
pub(crate) trait PretokenCountable<'a, S: BuildHasher + Default> {
|
|
10
|
+
fn pretoken_count(self) -> HashMap<Pretoken<'a>, usize, S>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl<'a, T, S> PretokenCountable<'a, S> for T
|
|
14
|
+
where
|
|
15
|
+
T: Iterator<Item = Pretoken<'a>>,
|
|
16
|
+
S: BuildHasher + Default,
|
|
17
|
+
{
|
|
18
|
+
fn pretoken_count(self) -> HashMap<Pretoken<'a>, usize, S> {
|
|
19
|
+
self.fold(HashMap::default(), |mut counts, token| {
|
|
20
|
+
*counts.entry(token).or_default() += 1;
|
|
21
|
+
counts
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub(crate) trait ParallelMergeCounts<K, V, S> {
|
|
27
|
+
fn par_merge_counts(self) -> HashMap<K, V, S>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl<T, K, V, S> ParallelMergeCounts<K, V, S> for T
|
|
31
|
+
where
|
|
32
|
+
T: ParallelIterator<Item = HashMap<K, V, S>>,
|
|
33
|
+
K: Eq + Hash,
|
|
34
|
+
V: AddAssign + Default,
|
|
35
|
+
S: BuildHasher + Default,
|
|
36
|
+
{
|
|
37
|
+
fn par_merge_counts(self) -> HashMap<K, V, S> {
|
|
38
|
+
self.reduce(HashMap::default, |mut acc, counts| {
|
|
39
|
+
if acc.is_empty() {
|
|
40
|
+
return counts;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (k, v) in counts {
|
|
44
|
+
*acc.entry(k).or_default() += v;
|
|
45
|
+
}
|
|
46
|
+
acc
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|