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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +3016 -0
  3. data/Cargo.toml +135 -0
  4. data/LICENSE +21 -0
  5. data/README.md +141 -0
  6. data/exe/gigatoken +9 -0
  7. data/ext/gigatoken/Cargo.toml +24 -0
  8. data/ext/gigatoken/extconf.rb +19 -0
  9. data/ext/gigatoken/src/error.rs +20 -0
  10. data/ext/gigatoken/src/gvl.rs +122 -0
  11. data/ext/gigatoken/src/lib.rs +50 -0
  12. data/ext/gigatoken/src/sentencepiece.rs +205 -0
  13. data/ext/gigatoken/src/sources.rs +207 -0
  14. data/ext/gigatoken/src/tokenizer.rs +571 -0
  15. data/lib/gigatoken/cli/bench.rb +64 -0
  16. data/lib/gigatoken/cli/support.rb +132 -0
  17. data/lib/gigatoken/cli/validate.rb +55 -0
  18. data/lib/gigatoken/cli.rb +18 -0
  19. data/lib/gigatoken/hub.rb +242 -0
  20. data/lib/gigatoken/packed_result.rb +48 -0
  21. data/lib/gigatoken/tokenizer.rb +117 -0
  22. data/lib/gigatoken/version.rb +5 -0
  23. data/lib/gigatoken.rb +29 -0
  24. data/rust-toolchain.toml +8 -0
  25. data/src/batch.rs +1808 -0
  26. data/src/bindings/bridge.rs +396 -0
  27. data/src/bindings/hub.rs +42 -0
  28. data/src/bindings/matcher.rs +114 -0
  29. data/src/bindings/mod.rs +14 -0
  30. data/src/bindings/padding.rs +177 -0
  31. data/src/bindings/pretokenize.rs +53 -0
  32. data/src/bindings/sources.rs +273 -0
  33. data/src/bindings/train.rs +125 -0
  34. data/src/bpe/mod.rs +1217 -0
  35. data/src/bpe/pretoken_cache.rs +495 -0
  36. data/src/bpe/sentencepiece.rs +1485 -0
  37. data/src/bpe/tiktoken.rs +2555 -0
  38. data/src/bpe_train.rs +351 -0
  39. data/src/input/decompress.rs +11 -0
  40. data/src/input/file_source.rs +514 -0
  41. data/src/input/jsonl.rs +94 -0
  42. data/src/input/mod.rs +333 -0
  43. data/src/input/parquet.rs +303 -0
  44. data/src/lib.rs +578 -0
  45. data/src/load_tokenizer/hf.rs +1036 -0
  46. data/src/load_tokenizer/hub.rs +344 -0
  47. data/src/load_tokenizer/mod.rs +3 -0
  48. data/src/load_tokenizer/tiktoken.rs +87 -0
  49. data/src/main.rs +95 -0
  50. data/src/pretokenize/fast/cl100k.rs +426 -0
  51. data/src/pretokenize/fast/cl100k_family.rs +891 -0
  52. data/src/pretokenize/fast/deepseek_v3.rs +605 -0
  53. data/src/pretokenize/fast/kimi.rs +281 -0
  54. data/src/pretokenize/fast/mask.rs +1486 -0
  55. data/src/pretokenize/fast/mod.rs +446 -0
  56. data/src/pretokenize/fast/nemotron.rs +138 -0
  57. data/src/pretokenize/fast/o200k.rs +347 -0
  58. data/src/pretokenize/fast/o200k_family.rs +1734 -0
  59. data/src/pretokenize/fast/olmo3.rs +505 -0
  60. data/src/pretokenize/fast/qwen2.rs +429 -0
  61. data/src/pretokenize/fast/qwen3_5.rs +541 -0
  62. data/src/pretokenize/fast/r50k.rs +1250 -0
  63. data/src/pretokenize/mod.rs +1079 -0
  64. data/src/pretokenize/options.rs +188 -0
  65. data/src/pretokenize/pretoken.rs +20 -0
  66. data/src/pretokenize/pretokenize_traits.rs +49 -0
  67. data/src/pretokenize/reference/avx512.rs +522 -0
  68. data/src/pretokenize/reference/combinator.rs +572 -0
  69. data/src/pretokenize/reference/mod.rs +28 -0
  70. data/src/pretokenize/reference/simd.rs +852 -0
  71. data/src/pretokenize/reference/state_machine.rs +365 -0
  72. data/src/pretokenize/unicode.rs +546 -0
  73. data/src/test_hub.rs +28 -0
  74. data/src/token.rs +42 -0
  75. 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
+ }