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,522 @@
|
|
|
1
|
+
#![allow(unsafe_op_in_unsafe_fn)]
|
|
2
|
+
//! AVX-512 accelerated pretokenizer for the GPT-2 regex:
|
|
3
|
+
//! `'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+`
|
|
4
|
+
//!
|
|
5
|
+
//! Uses AVX-512BW/VL byte-level comparisons with k-register masks for fast
|
|
6
|
+
//! scanning of character runs.
|
|
7
|
+
|
|
8
|
+
use crate::pretokenize::pretoken::Pretoken;
|
|
9
|
+
use crate::pretokenize::unicode;
|
|
10
|
+
|
|
11
|
+
use std::arch::x86_64::*;
|
|
12
|
+
|
|
13
|
+
// ==========================================================================
|
|
14
|
+
// Public iterator
|
|
15
|
+
// ==========================================================================
|
|
16
|
+
|
|
17
|
+
pub struct Avx512PretokenizerIter<'a> {
|
|
18
|
+
bytes: &'a [u8],
|
|
19
|
+
pos: usize,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl<'a> Avx512PretokenizerIter<'a> {
|
|
23
|
+
#[inline]
|
|
24
|
+
pub fn new(input: &'a [u8]) -> Self {
|
|
25
|
+
Self {
|
|
26
|
+
bytes: input,
|
|
27
|
+
pos: 0,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl<'a> Iterator for Avx512PretokenizerIter<'a> {
|
|
33
|
+
type Item = Pretoken<'a>;
|
|
34
|
+
|
|
35
|
+
#[inline]
|
|
36
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
37
|
+
let bytes = self.bytes;
|
|
38
|
+
let pos = self.pos;
|
|
39
|
+
if pos >= bytes.len() {
|
|
40
|
+
return None;
|
|
41
|
+
}
|
|
42
|
+
let end = unsafe { find_pretoken_end(bytes, pos) };
|
|
43
|
+
self.pos = end;
|
|
44
|
+
Some(Pretoken(unsafe { bytes.get_unchecked(pos..end) }))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#[inline]
|
|
48
|
+
fn count(self) -> usize {
|
|
49
|
+
count_pretokens_inner(self.bytes, self.pos)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Two-cursor counting: splits input at a safe boundary and processes
|
|
54
|
+
/// both halves interleaved, allowing OoO execution to overlap latencies.
|
|
55
|
+
#[inline]
|
|
56
|
+
fn count_pretokens_inner(bytes: &[u8], start: usize) -> usize {
|
|
57
|
+
let len = bytes.len();
|
|
58
|
+
if len - start < 4096 {
|
|
59
|
+
return count_simple(bytes, start);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Find a safe split near the middle: a newline preceded by a non-ws byte
|
|
63
|
+
let mid = start + (len - start) / 2;
|
|
64
|
+
let split = find_split(bytes, mid);
|
|
65
|
+
if split == 0 {
|
|
66
|
+
return count_simple(bytes, start);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let mut p1 = start;
|
|
70
|
+
let mut p2 = split;
|
|
71
|
+
let mut count = 0usize;
|
|
72
|
+
|
|
73
|
+
// Interleaved processing: two independent cursor chains
|
|
74
|
+
while p1 < split && p2 < len {
|
|
75
|
+
p1 = unsafe { find_pretoken_end(bytes, p1) };
|
|
76
|
+
p2 = unsafe { find_pretoken_end(bytes, p2) };
|
|
77
|
+
count += 2;
|
|
78
|
+
}
|
|
79
|
+
while p1 < split {
|
|
80
|
+
p1 = unsafe { find_pretoken_end(bytes, p1) };
|
|
81
|
+
count += 1;
|
|
82
|
+
}
|
|
83
|
+
while p2 < len {
|
|
84
|
+
p2 = unsafe { find_pretoken_end(bytes, p2) };
|
|
85
|
+
count += 1;
|
|
86
|
+
}
|
|
87
|
+
count
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Simple single-cursor counting loop.
|
|
91
|
+
fn count_simple(bytes: &[u8], mut pos: usize) -> usize {
|
|
92
|
+
let mut count = 0usize;
|
|
93
|
+
while pos < bytes.len() {
|
|
94
|
+
pos = unsafe { find_pretoken_end(bytes, pos) };
|
|
95
|
+
count += 1;
|
|
96
|
+
}
|
|
97
|
+
count
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Find a token boundary near `target` to split the input.
|
|
101
|
+
/// Returns a position where a new token is guaranteed to start,
|
|
102
|
+
/// or 0 if no safe split found.
|
|
103
|
+
fn find_split(bytes: &[u8], target: usize) -> usize {
|
|
104
|
+
// Search forward for a newline followed by a non-whitespace byte
|
|
105
|
+
let search_end = (target + 256).min(bytes.len());
|
|
106
|
+
for i in target..search_end {
|
|
107
|
+
if bytes[i] == b'\n' {
|
|
108
|
+
let after = i + 1;
|
|
109
|
+
if after < bytes.len() && bytes[after] != b' ' && bytes[after].wrapping_sub(9) >= 5 {
|
|
110
|
+
return after;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Search backward
|
|
115
|
+
let search_start = target.saturating_sub(256);
|
|
116
|
+
for i in (search_start..target).rev() {
|
|
117
|
+
if bytes[i] == b'\n' {
|
|
118
|
+
let after = i + 1;
|
|
119
|
+
if after < bytes.len() && bytes[after] != b' ' && bytes[after].wrapping_sub(9) >= 5 {
|
|
120
|
+
return after;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ==========================================================================
|
|
128
|
+
// Dispatch — comparison-based for minimal data dependencies
|
|
129
|
+
// ==========================================================================
|
|
130
|
+
|
|
131
|
+
#[inline]
|
|
132
|
+
unsafe fn find_pretoken_end(bytes: &[u8], pos: usize) -> usize {
|
|
133
|
+
let first = *bytes.get_unchecked(pos);
|
|
134
|
+
|
|
135
|
+
// Hot path: ASCII letter (~40% of tokens start with a letter)
|
|
136
|
+
if (first | 0x20).wrapping_sub(b'a') < 26 {
|
|
137
|
+
return scan_full_letter_run(bytes, pos);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Second hottest: space before content
|
|
141
|
+
if first == b' ' {
|
|
142
|
+
return space_start(bytes, pos);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if first >= 0x80 {
|
|
146
|
+
return non_ascii_start(bytes, pos);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if first.wrapping_sub(b'0') < 10 {
|
|
150
|
+
return scan_full_digit_run(bytes, pos);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if first == b'\'' {
|
|
154
|
+
return contraction_or_other(bytes, pos);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if first.wrapping_sub(9) < 5 {
|
|
158
|
+
return whitespace_boundary(bytes, pos);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
scan_full_other_run(bytes, pos)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[inline]
|
|
165
|
+
unsafe fn space_start(bytes: &[u8], pos: usize) -> usize {
|
|
166
|
+
let next = pos + 1;
|
|
167
|
+
if next >= bytes.len() {
|
|
168
|
+
return next;
|
|
169
|
+
}
|
|
170
|
+
let b = *bytes.get_unchecked(next);
|
|
171
|
+
|
|
172
|
+
if (b | 0x20).wrapping_sub(b'a') < 26 {
|
|
173
|
+
return scan_full_letter_run(bytes, next);
|
|
174
|
+
}
|
|
175
|
+
if b.wrapping_sub(b'0') < 10 {
|
|
176
|
+
return scan_full_digit_run(bytes, next);
|
|
177
|
+
}
|
|
178
|
+
if b == b' ' || b.wrapping_sub(9) < 5 {
|
|
179
|
+
return whitespace_boundary(bytes, pos);
|
|
180
|
+
}
|
|
181
|
+
if b >= 0x80 {
|
|
182
|
+
return space_then_non_ascii(bytes, pos, next);
|
|
183
|
+
}
|
|
184
|
+
scan_full_other_run(bytes, next)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[cold]
|
|
188
|
+
unsafe fn space_then_non_ascii(bytes: &[u8], space_pos: usize, char_pos: usize) -> usize {
|
|
189
|
+
let c = decode_char_unchecked(bytes, char_pos);
|
|
190
|
+
if unicode::is_letter(c) {
|
|
191
|
+
scan_letter_after_unicode(bytes, char_pos, c)
|
|
192
|
+
} else if unicode::is_number(c) {
|
|
193
|
+
scan_digit_after_unicode(bytes, char_pos, c)
|
|
194
|
+
} else if unicode::is_whitespace(c) {
|
|
195
|
+
whitespace_boundary(bytes, space_pos)
|
|
196
|
+
} else {
|
|
197
|
+
scan_other_after_unicode(bytes, char_pos, c)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[cold]
|
|
202
|
+
unsafe fn non_ascii_start(bytes: &[u8], pos: usize) -> usize {
|
|
203
|
+
let c = decode_char_unchecked(bytes, pos);
|
|
204
|
+
if unicode::is_letter(c) {
|
|
205
|
+
scan_letter_after_unicode(bytes, pos, c)
|
|
206
|
+
} else if unicode::is_number(c) {
|
|
207
|
+
scan_digit_after_unicode(bytes, pos, c)
|
|
208
|
+
} else if unicode::is_whitespace(c) {
|
|
209
|
+
whitespace_boundary(bytes, pos)
|
|
210
|
+
} else {
|
|
211
|
+
scan_other_after_unicode(bytes, pos, c)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
// Contractions
|
|
217
|
+
// ==========================================================================
|
|
218
|
+
|
|
219
|
+
#[inline]
|
|
220
|
+
unsafe fn contraction_or_other(bytes: &[u8], pos: usize) -> usize {
|
|
221
|
+
let a = pos + 1;
|
|
222
|
+
if a < bytes.len() {
|
|
223
|
+
match *bytes.get_unchecked(a) {
|
|
224
|
+
b's' | b'd' | b'm' | b't' => return a + 1,
|
|
225
|
+
b'l' if a + 1 < bytes.len() && *bytes.get_unchecked(a + 1) == b'l' => return a + 2,
|
|
226
|
+
b'v' if a + 1 < bytes.len() && *bytes.get_unchecked(a + 1) == b'e' => return a + 2,
|
|
227
|
+
b'r' if a + 1 < bytes.len() && *bytes.get_unchecked(a + 1) == b'e' => return a + 2,
|
|
228
|
+
_ => {}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
scan_full_other_run(bytes, pos)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ==========================================================================
|
|
235
|
+
// Whitespace boundary: \s+(?!\S) | \s+
|
|
236
|
+
// ==========================================================================
|
|
237
|
+
|
|
238
|
+
#[inline]
|
|
239
|
+
unsafe fn whitespace_boundary(bytes: &[u8], start: usize) -> usize {
|
|
240
|
+
let len = bytes.len();
|
|
241
|
+
let mut i = start;
|
|
242
|
+
|
|
243
|
+
while i < len {
|
|
244
|
+
let b = *bytes.get_unchecked(i);
|
|
245
|
+
if b == b' ' || b.wrapping_sub(9) < 5 {
|
|
246
|
+
i += 1;
|
|
247
|
+
} else if b >= 0x80 {
|
|
248
|
+
let c = decode_char_unchecked(bytes, i);
|
|
249
|
+
if unicode::is_whitespace(c) {
|
|
250
|
+
i += c.len_utf8();
|
|
251
|
+
} else {
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if i >= len {
|
|
260
|
+
return i;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let mut last_start = i - 1;
|
|
264
|
+
while last_start > start && *bytes.get_unchecked(last_start) & 0xC0 == 0x80 {
|
|
265
|
+
last_start -= 1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if last_start <= start {
|
|
269
|
+
return i;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
last_start
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ==========================================================================
|
|
276
|
+
// SIMD scan primitives — 32-byte chunks
|
|
277
|
+
// ==========================================================================
|
|
278
|
+
|
|
279
|
+
#[inline]
|
|
280
|
+
unsafe fn scan_ascii_letters(bytes: &[u8], start: usize) -> usize {
|
|
281
|
+
let ptr = bytes.as_ptr();
|
|
282
|
+
let len = bytes.len();
|
|
283
|
+
let mut i = start;
|
|
284
|
+
|
|
285
|
+
while i + 32 <= len {
|
|
286
|
+
let chunk = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
|
|
287
|
+
let lower = _mm256_or_si256(chunk, _mm256_set1_epi8(0x20u8 as i8));
|
|
288
|
+
let diff = _mm256_sub_epi8(lower, _mm256_set1_epi8(b'a' as i8));
|
|
289
|
+
let mask: u32 = _mm256_cmplt_epu8_mask(diff, _mm256_set1_epi8(26));
|
|
290
|
+
if mask != u32::MAX {
|
|
291
|
+
return i + mask.trailing_ones() as usize;
|
|
292
|
+
}
|
|
293
|
+
i += 32;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
while i < len && (*ptr.add(i) | 0x20).wrapping_sub(b'a') < 26 {
|
|
297
|
+
i += 1;
|
|
298
|
+
}
|
|
299
|
+
i
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#[inline]
|
|
303
|
+
unsafe fn scan_ascii_digits(bytes: &[u8], start: usize) -> usize {
|
|
304
|
+
let ptr = bytes.as_ptr();
|
|
305
|
+
let len = bytes.len();
|
|
306
|
+
let mut i = start;
|
|
307
|
+
|
|
308
|
+
while i + 32 <= len {
|
|
309
|
+
let chunk = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
|
|
310
|
+
let diff = _mm256_sub_epi8(chunk, _mm256_set1_epi8(b'0' as i8));
|
|
311
|
+
let mask: u32 = _mm256_cmplt_epu8_mask(diff, _mm256_set1_epi8(10));
|
|
312
|
+
if mask != u32::MAX {
|
|
313
|
+
return i + mask.trailing_ones() as usize;
|
|
314
|
+
}
|
|
315
|
+
i += 32;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
while i < len && (*ptr.add(i)).wrapping_sub(b'0') < 10 {
|
|
319
|
+
i += 1;
|
|
320
|
+
}
|
|
321
|
+
i
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#[inline]
|
|
325
|
+
unsafe fn scan_ascii_other(bytes: &[u8], start: usize) -> usize {
|
|
326
|
+
let ptr = bytes.as_ptr();
|
|
327
|
+
let len = bytes.len();
|
|
328
|
+
let mut i = start;
|
|
329
|
+
|
|
330
|
+
while i + 32 <= len {
|
|
331
|
+
let chunk = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
|
|
332
|
+
let lower = _mm256_or_si256(chunk, _mm256_set1_epi8(0x20u8 as i8));
|
|
333
|
+
let diff_l = _mm256_sub_epi8(lower, _mm256_set1_epi8(b'a' as i8));
|
|
334
|
+
let is_letter: u32 = _mm256_cmplt_epu8_mask(diff_l, _mm256_set1_epi8(26));
|
|
335
|
+
let diff_d = _mm256_sub_epi8(chunk, _mm256_set1_epi8(b'0' as i8));
|
|
336
|
+
let is_digit: u32 = _mm256_cmplt_epu8_mask(diff_d, _mm256_set1_epi8(10));
|
|
337
|
+
let is_space: u32 = _mm256_cmpeq_epi8_mask(chunk, _mm256_set1_epi8(b' ' as i8));
|
|
338
|
+
let diff_w = _mm256_sub_epi8(chunk, _mm256_set1_epi8(9));
|
|
339
|
+
let is_ctrl_ws: u32 = _mm256_cmplt_epu8_mask(diff_w, _mm256_set1_epi8(5));
|
|
340
|
+
let is_high: u32 = _mm256_movepi8_mask(chunk);
|
|
341
|
+
let not_other = is_letter | is_digit | is_space | is_ctrl_ws | is_high;
|
|
342
|
+
if not_other != 0 {
|
|
343
|
+
return i + not_other.trailing_zeros() as usize;
|
|
344
|
+
}
|
|
345
|
+
i += 32;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
while i < len {
|
|
349
|
+
let b = *ptr.add(i);
|
|
350
|
+
if b >= 0x80
|
|
351
|
+
|| (b | 0x20).wrapping_sub(b'a') < 26
|
|
352
|
+
|| b.wrapping_sub(b'0') < 10
|
|
353
|
+
|| b == b' '
|
|
354
|
+
|| b.wrapping_sub(9) < 5
|
|
355
|
+
{
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
i += 1;
|
|
359
|
+
}
|
|
360
|
+
i
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ==========================================================================
|
|
364
|
+
// Full run scanners: ASCII SIMD + Unicode extension
|
|
365
|
+
// ==========================================================================
|
|
366
|
+
|
|
367
|
+
#[inline]
|
|
368
|
+
unsafe fn scan_full_letter_run(bytes: &[u8], start: usize) -> usize {
|
|
369
|
+
let mut i = scan_ascii_letters(bytes, start);
|
|
370
|
+
while i < bytes.len() && *bytes.get_unchecked(i) >= 0x80 {
|
|
371
|
+
let c = decode_char_unchecked(bytes, i);
|
|
372
|
+
if unicode::is_letter(c) {
|
|
373
|
+
i += c.len_utf8();
|
|
374
|
+
i = scan_ascii_letters(bytes, i);
|
|
375
|
+
} else {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
i
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[inline]
|
|
383
|
+
unsafe fn scan_full_digit_run(bytes: &[u8], start: usize) -> usize {
|
|
384
|
+
let mut i = scan_ascii_digits(bytes, start);
|
|
385
|
+
while i < bytes.len() && *bytes.get_unchecked(i) >= 0x80 {
|
|
386
|
+
let c = decode_char_unchecked(bytes, i);
|
|
387
|
+
if unicode::is_number(c) {
|
|
388
|
+
i += c.len_utf8();
|
|
389
|
+
i = scan_ascii_digits(bytes, i);
|
|
390
|
+
} else {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
i
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#[inline]
|
|
398
|
+
unsafe fn scan_full_other_run(bytes: &[u8], start: usize) -> usize {
|
|
399
|
+
let mut i = scan_ascii_other(bytes, start);
|
|
400
|
+
while i < bytes.len() && *bytes.get_unchecked(i) >= 0x80 {
|
|
401
|
+
let c = decode_char_unchecked(bytes, i);
|
|
402
|
+
if unicode::is_other_complete(c) {
|
|
403
|
+
i += c.len_utf8();
|
|
404
|
+
i = scan_ascii_other(bytes, i);
|
|
405
|
+
} else {
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
i
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
#[cold]
|
|
413
|
+
unsafe fn scan_letter_after_unicode(bytes: &[u8], pos: usize, c: char) -> usize {
|
|
414
|
+
scan_full_letter_run(bytes, pos + c.len_utf8())
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#[cold]
|
|
418
|
+
unsafe fn scan_digit_after_unicode(bytes: &[u8], pos: usize, c: char) -> usize {
|
|
419
|
+
scan_full_digit_run(bytes, pos + c.len_utf8())
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#[cold]
|
|
423
|
+
unsafe fn scan_other_after_unicode(bytes: &[u8], pos: usize, c: char) -> usize {
|
|
424
|
+
scan_full_other_run(bytes, pos + c.len_utf8())
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ==========================================================================
|
|
428
|
+
// Utility
|
|
429
|
+
// ==========================================================================
|
|
430
|
+
|
|
431
|
+
#[inline]
|
|
432
|
+
unsafe fn decode_char_unchecked(bytes: &[u8], pos: usize) -> char {
|
|
433
|
+
std::str::from_utf8_unchecked(bytes.get_unchecked(pos..))
|
|
434
|
+
.chars()
|
|
435
|
+
.next()
|
|
436
|
+
.unwrap_unchecked()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ==========================================================================
|
|
440
|
+
// Tests
|
|
441
|
+
// ==========================================================================
|
|
442
|
+
|
|
443
|
+
#[cfg(test)]
|
|
444
|
+
mod tests {
|
|
445
|
+
use super::*;
|
|
446
|
+
use std::cmp::min;
|
|
447
|
+
|
|
448
|
+
#[test]
|
|
449
|
+
fn avx512_matches_state_machine() {
|
|
450
|
+
let data_dir = std::env::home_dir().unwrap().join("data");
|
|
451
|
+
let input = std::fs::read(data_dir.join("TinyStoriesV2-GPT4-valid.txt")).unwrap();
|
|
452
|
+
let standard = crate::pretokenize::pretokenize_as_iter(&input);
|
|
453
|
+
let avx512 = Avx512PretokenizerIter::new(&input);
|
|
454
|
+
|
|
455
|
+
let mut count = 0usize;
|
|
456
|
+
for (a, b) in standard.zip(avx512) {
|
|
457
|
+
if a.0 != b.0 {
|
|
458
|
+
let a_offset = a.0.as_ptr() as usize - input.as_ptr() as usize;
|
|
459
|
+
let region_start = a_offset.saturating_sub(32);
|
|
460
|
+
let region_end = min(input.len(), a_offset + 64);
|
|
461
|
+
let context = String::from_utf8_lossy(&input[region_start..region_end]);
|
|
462
|
+
panic!(
|
|
463
|
+
"Mismatch at token {count}: sm={:?} avx={:?}\nContext: {context:?}",
|
|
464
|
+
String::from_utf8_lossy(a.0),
|
|
465
|
+
String::from_utf8_lossy(b.0),
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
count += 1;
|
|
469
|
+
}
|
|
470
|
+
eprintln!("All {count} tokens match.");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
#[test]
|
|
474
|
+
fn avx512_matches_state_machine_owt() {
|
|
475
|
+
let data_dir = std::env::home_dir().unwrap().join("data");
|
|
476
|
+
let all_bytes =
|
|
477
|
+
std::fs::read(data_dir.join("owt_train.txt")).expect("Could not read owt_train.txt");
|
|
478
|
+
let max = 5_000_000.min(all_bytes.len());
|
|
479
|
+
let mut end = max;
|
|
480
|
+
while end > 0 && std::str::from_utf8(&all_bytes[..end]).is_err() {
|
|
481
|
+
end -= 1;
|
|
482
|
+
}
|
|
483
|
+
let input = &all_bytes[..end];
|
|
484
|
+
|
|
485
|
+
let standard = crate::pretokenize::pretokenize_as_iter(input);
|
|
486
|
+
let avx512 = Avx512PretokenizerIter::new(input);
|
|
487
|
+
|
|
488
|
+
let mut count = 0usize;
|
|
489
|
+
for (a, b) in standard.zip(avx512) {
|
|
490
|
+
if a.0 != b.0 {
|
|
491
|
+
let a_offset = a.0.as_ptr() as usize - input.as_ptr() as usize;
|
|
492
|
+
let region_start = a_offset.saturating_sub(32);
|
|
493
|
+
let region_end = min(input.len(), a_offset + 64);
|
|
494
|
+
let context = String::from_utf8_lossy(&input[region_start..region_end]);
|
|
495
|
+
panic!(
|
|
496
|
+
"Mismatch at token {count}: sm={:?} avx={:?}\nContext: {context:?}",
|
|
497
|
+
String::from_utf8_lossy(a.0),
|
|
498
|
+
String::from_utf8_lossy(b.0),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
count += 1;
|
|
502
|
+
}
|
|
503
|
+
eprintln!("All {count} OWT tokens match.");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
#[test]
|
|
507
|
+
fn avx512_count_matches() {
|
|
508
|
+
let data_dir = std::env::home_dir().unwrap().join("data");
|
|
509
|
+
let all_bytes =
|
|
510
|
+
std::fs::read(data_dir.join("owt_train.txt")).expect("Could not read owt_train.txt");
|
|
511
|
+
let max = 5_000_000.min(all_bytes.len());
|
|
512
|
+
let mut end = max;
|
|
513
|
+
while end > 0 && std::str::from_utf8(&all_bytes[..end]).is_err() {
|
|
514
|
+
end -= 1;
|
|
515
|
+
}
|
|
516
|
+
let input = &all_bytes[..end];
|
|
517
|
+
|
|
518
|
+
let sm_count = crate::pretokenize::pretokenize_as_iter(input).count();
|
|
519
|
+
let avx_count = Avx512PretokenizerIter::new(input).count();
|
|
520
|
+
assert_eq!(sm_count, avx_count, "Token counts differ");
|
|
521
|
+
}
|
|
522
|
+
}
|