1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
//! Pretokenization: split documents into pretokens following a tokenizer's
//! pretokenization regex.
//!
//! The production implementations live in `fast` (one submodule per scheme:
//! `fast::r50k` for GPT-2, `fast::cl100k` for GPT-4, ...), selected via
//! [`PretokenizerType`]. Superseded designs (state machine, combinator,
//! SIMD prototypes) live in [`reference`] as benchmark baselines and test
//! oracles — nothing there runs in the encode path.
//!
//! The main entry points are:
//! - `pretokenize_as_iter`: iterate pretokens of a `&[u8]` (r50k scheme)
//! - `PretokenizerType::pretokenize`: iterate pretokens of any scheme
//! - `Pretokenize` trait: `doc.pretokens()` on any `&[u8]`
// Fast scanners carry target-specific helpers and iterator adapters that are
// intentionally dormant on some architectures and tokenizer families.
pub use cratePretoken;
pub use ;
pub use ;
/// Default document separator used in common training corpora.
pub const DEFAULT_SEPARATOR: & = b"<|endoftext|>";
/// Iterate the pretokens of `bytes` using the production (r50k) pretokenizer.
// ---------------------------------------------------------------------------
// Batched pretoken pulling (the encode loop's input interface)
// ---------------------------------------------------------------------------
/// Chunk size of [`PretokenSpans::fill_spans_keyed`] — the live entries of
/// one [`SpanBatch`] fill.
pub const PRETOKEN_CHUNK: usize = 256;
/// Both 64-bit halves of the per-length pack mask, in scalar ALU ops. A
/// u128 `MAX >> s` lowers to a multi-instruction sequence and the 16-entry
/// table this replaces put a dependent L1 load (2.43% of process) on the
/// `n → key → store` chain; per-half variable shifts are single 1-cycle
/// ops, so the halves cost two independent 3-deep chains and no load port.
/// The length tag (`n << 120`) touches only the high half and is OR'd in
/// by the caller. `const`: the phase-B emission loop's `PACK_MASK_TABLE`
/// (see `fast::mask`) is built from this at compile time, so the ALU and
/// table forms cannot drift apart.
pub const
// The key packers below (`pack_pretoken_key`, `fill_spans_keyed_with_buf`,
// phase B of the two-phase walker) read span bytes as native-endian words
// and mask, and the emit loop stores packed token lanes as one native-endian
// word — all little-endian layouts. A big-endian build would silently
// produce wrong keys and swapped tokens, so refuse to compile instead.
compile_error!;
/// Pack a pretoken of ≤ 15 bytes into a `u128` cache key: bytes in the low
/// 15 lanes, length in the top byte (so keys of different lengths never
/// collide, and a real key is never 0). Returns `None` for longer
/// pretokens, which use the slice-keyed fallback map.
///
/// The common path is a single unaligned 16-byte load followed by a mask,
/// avoiding both a variable-length `memcpy` and per-byte branching. The
/// load is only taken when it cannot cross a page boundary, so it can
/// never touch an unmapped page; the rare near-boundary case falls back to
/// a plain copy. Both paths produce the identical key.
pub
/// Hash of a packed pretoken key. Quality is noncritical for correctness
/// (the table compares full keys), but every consumer — the fill loops
/// (`fill_spans_keyed_with{,_buf}`, `fill_spans_two_phase`),
/// `ShortPretokenCache::grow`'s rehash, and the vocab-seeding paths
/// (`seeded_pretoken_cache`, `add_special_token`, `fork_sized`) — must
/// compute the same function of the key: the arms below produce different
/// values and may never mix in one process image. On aarch64 the arm is
/// picked at compile time; on x86_64 it is picked per process by
/// [`crc_hash_selected`], an immutable pure function of the CPU — this
/// entry point branches on that bit (cheap at the cold/slow sites that
/// use it, including the test-only [`fill_spans_keyed_with`]), and the
/// two hot fill loops instead embed one arm per monomorphization,
/// dispatched once per fill on the same bit (see [`fill_span_hash`]), so
/// the same key always hashes the same way. All
/// arms map key 0 to hash 0, which the fill loops' long-pretoken route
/// stores.
pub
/// The multiply-fold arm of [`pretoken_key_hash`]: one folded multiply,
/// the cheapest mix whose low bits still see every key bit. Every target
/// can execute it; it is the process's hash wherever no hardware CRC arm
/// applies. Maps key 0 to hash 0 (0 · M = 0).
// no cfg arm references it under aarch64 + crc
/// The hardware CRC32C (SSE4.2) arm of [`pretoken_key_hash`]: same shape
/// and rationale as the aarch64 CRC32 arm — linear over GF(2) so the low
/// bits (the table index) see every key bit, 3-cycle latency and one µop
/// per `crc32` on Zen 2 (two chained ops vs the 5-op multiply fold), and
/// `_mm_crc32_u64(0, 0) == 0` preserves the key 0 -> hash 0 property the
/// fill loops' long-pretoken route stores.
///
/// `sse4.2` is NOT in baseline x86-64, so distributed wheels cannot gate
/// this arm at compile time; it is instead selected per process by
/// [`crc_hash_selected`] and reached only through call sites guarded by
/// that bit. Builds with `sse4.2` statically enabled (`-C
/// target-cpu=znver2`, any x86-64-v2+ setting) fold the guards away and
/// keep the pure-CRC codegen of a compile-time arm.
///
/// # Safety
///
/// The CPU must support SSE4.2: callers reach this only after
/// [`crc_hash_selected`] returned true (directly, or structurally via a
/// `fill_span_hash::<true>` monomorphization — see its contract), or from
/// a build with `sse4.2` statically enabled.
unsafe
/// Does this process hash pretoken keys with CRC32C (x86_64)? A pure
/// function of the CPU, so one immutable answer for the process lifetime:
/// [`pretoken_key_hash`] and every fill-loop dispatch branch on this same
/// bit, which is what keeps the one-hash-per-process invariant airtight
/// without a per-key runtime check in the hot loops. std caches the
/// CPUID result, so after the first call this is a relaxed atomic load +
/// bit test; builds with `sse4.2` statically enabled const-fold it to
/// `true`.
pub
/// Per-span hash for the monomorphized fill-loop bodies:
/// [`pretoken_key_hash`] with the x86_64 per-process selection hoisted
/// out of the per-span path. On x86_64 the two instantiations embed one
/// arm each, and each is reachable only under the matching value of
/// [`crc_hash_selected`]:
///
/// - `X86_CRC = true` bodies are called exclusively from the
/// `#[target_feature(enable = "sse4.2")]` fill wrappers, which their
/// dispatchers enter only when `crc_hash_selected()` is true;
/// - `X86_CRC = false` bodies are called exclusively from the dispatchers'
/// other arm, i.e. only when `crc_hash_selected()` is false (with
/// `sse4.2` statically enabled that arm is statically dead).
///
/// So every instantiation agrees with what [`pretoken_key_hash`] returns
/// for the same key in the same process. Off x86_64 the parameter is
/// ignored and this IS [`pretoken_key_hash`].
pub
/// One batch slot: a pretoken span with its packed cache key and key hash,
/// as one 32-byte record (half a cache line, never straddling one thanks
/// to the alignment).
///
/// AoS instead of the previous three parallel arrays for dataflow on both
/// sides: the fill loops store one record with two `stp`s on a single
/// store stream (the parallel arrays cost 4 store µops across 3 streams —
/// the split u128 key store alone was two), and the probe loop's per-`i`
/// `(key, hash)` read touches one cache line instead of three.
///
/// `meta` carries the field the consumer needs next, keyed on `key`:
/// - `key != 0` (short pretoken, ≤ 15 bytes): `meta` is the full 64-bit
/// key hash. The span length rides in the key's top byte, so `ptr` +
/// `key >> 120` reconstructs the span on the (rare) slow path.
/// - `key == 0` (long pretoken, or an empty span through the public
/// adapter): `meta` is the span length in bytes. The hash is not stored:
/// the long route never probes the short table, and
/// `pretoken_key_hash(0) == 0` is what the old layout recorded anyway.
/// Prefetching `meta` as if it were a hash touches an arbitrary
/// (masked, in-bounds) table line — harmless, long pretokens are rare.
///
/// Fields are `pub(crate)`: only the in-crate fill loops may write entries
/// (safe external writes of an arbitrary `ptr`/`key` would let safe code
/// drive [`SpanBatch::span`]'s `from_raw_parts` with garbage — see the
/// [`PretokenSpans`] safety contract).
pub
const _: = assert!;
/// Readable slack entries past a full chunk, so the emit loop's
/// prefetch-ahead `entries[i + D].meta` load needs no index clamp (a
/// per-pretoken `add + cmp + csel` in the hottest loop). Slack entries
/// are never written by a fill; prefetching a stale or zero `meta`
/// requests an arbitrary masked (in-bounds) table line — harmless.
pub const SPAN_BATCH_SLACK: usize = 16;
/// One chunk of pretoken spans with their packed cache keys (0 = longer
/// than 15 bytes, routed to the slice-keyed fallback map) and key hashes,
/// filled by [`PretokenSpans::fill_spans_keyed`]. See [`BatchEntry`] for
/// the record layout. Fills only ever write the first [`PRETOKEN_CHUNK`]
/// entries; the tail is prefetch slack (see [`SPAN_BATCH_SLACK`]).
/// A source of pretoken spans, pulled a chunk at a time with their cache
/// keys derived on the way out.
///
/// `Tokenizer::memoized_encode` consumes pretokens through this instead of
/// `Iterator` for codegen reasons: pulling happens in a dedicated
/// out-of-line loop, so the pretokenizer state is register-allocated
/// across the whole chunk (inlined into the register-starved encode loop
/// it lives in stack slots, ~9 cycles/pretoken of spill traffic on Zen 2).
/// Key packing, hashing, and the cache-line prefetch ride along in the
/// same loop because the span walker is a serial dependency chain (IPC
/// ~1.7 standalone): the independent per-span key math fills its idle
/// issue slots nearly for free, where a separate pass paid for it in full.
///
/// # Safety
///
/// The consumer trusts every fill unconditionally: after
/// [`Self::fill_spans_keyed`] returns `n`, the emit loop calls
/// [`SpanBatch::span`] (a raw `from_raw_parts`) on any entry `i < n`.
/// Implementations must therefore uphold, for every call returning `n`:
///
/// - `batch.entries[0..n]` were all written by THIS call (no stale entries
/// counted), and
/// - each written entry holds a valid `(ptr, len)` into caller-live input
/// bytes of lifetime `'a`: `ptr` non-null and readable for
/// [`BatchEntry::span_len`] bytes, with `key`/`meta` derived from exactly
/// those bytes via `pack_pretoken_key`/`pretoken_key_hash` semantics.
///
/// The entry fields are `pub(crate)`, so implementations outside this
/// crate cannot write entries at all and can only soundly return 0; the
/// in-crate fill helpers (`fill_spans_keyed_with{,_buf}`,
/// `fill_spans_two_phase`) uphold the contract.
pub unsafe
/// Shared body of the iterator-backed [`PretokenSpans`] implementations
/// (spans with no single backing buffer): pull spans from `next` and derive
/// each one's key, hash, and prefetch on the way out. `#[inline(always)]`
/// so each `#[inline(never)]` implementation fuses it with its span walker
/// into a single out-of-line loop (see the trait docs for why that fusion
/// matters). Sources that walk one backing slice use
/// [`fill_spans_keyed_with_buf`] instead.
///
/// No production caller walks this path — the concrete pretokenizers and
/// the dispatch enum all fill through [`fill_spans_keyed_with_buf`] or
/// `fast::fill_spans_two_phase` — so unlike those two, it takes no
/// CRC-monomorphized wrapper: [`pretoken_key_hash`]'s per-span dispatch
/// branch (same process-immutable [`crc_hash_selected`] bit, so hash
/// values agree with every other site) is irrelevant off the hot path.
pub
/// [`fill_spans_keyed_with`] for span sources that walk a single backing
/// slice, with `next` yielding `(start, end)` byte offsets into `bytes`.
/// Knowing the buffer removes the two data-dependent branches of
/// [`pack_pretoken_key`] from the per-span path:
///
/// - the `> 15` long-pretoken route becomes a select — for a long span the
/// 16-byte load sits entirely inside the span, so it needs no guard, and
/// the select (not the mask clamp) provides the key-0 routing;
/// - the per-span page-boundary check (mispredict-prone: ~0.4% of spans on
/// 4 KiB pages) becomes one buffer-end bound, hoisted per fill and false
/// only for short spans starting in the last 15 bytes of `bytes` — once
/// per input, so the branch predicts ~perfectly.
///
/// Empty spans cannot occur (`next` contract: `start < end`), so the key-0
/// route is exactly "longer than 15 bytes", as in the fallible packer.
pub
/// The SSE4.2 (CRC-hash) monomorphization of [`fill_spans_keyed_with_buf`].
///
/// # Safety
///
/// The CPU must support SSE4.2 ([`crc_hash_selected`] must have returned
/// true).
unsafe
/// [`fill_spans_keyed_with_buf`]'s loop body, monomorphized on the hash
/// arm (`X86_CRC` — see [`fill_span_hash`]'s reachability contract).
/// Adapter giving any pretoken iterator (reference pretokenizers, tests,
/// custom sources) the [`PretokenSpans`] interface. The `Fast*`
/// pretokenizers implement the trait directly over their walker state
/// instead (see `fast::fill_spans_keyed_mask`): routing them through
/// `Iterator::next` left the (large, `#[inline(always)]`) `next_span`
/// un-inlined behind a real call — measured cost in
/// `fast::fill_spans_keyed_mask`'s docs.
;
// SAFETY: delegates to `fill_spans_keyed_with`, which writes exactly the
// first `n` entries from the iterator's live `'a` spans.
unsafe
// ---------------------------------------------------------------------------
// Pretokenize trait — Layer 3
// ---------------------------------------------------------------------------
/// Anything that can be split into a stream of pretokens.
// ---------------------------------------------------------------------------
// Pretoken-safe document splitting
// ---------------------------------------------------------------------------
/// Split `bytes` into ranges of roughly `target` bytes whose boundaries are
/// pretoken boundaries under every supported pretokenization scheme, so
/// encoding the ranges independently and concatenating the token streams is
/// identical to encoding `bytes` in one pass.
///
/// A boundary sits on a space that is preceded by an ASCII alphanumeric and
/// followed by an ASCII letter ("…word word…"). No scheme's pretoken can
/// cross such a point: whitespace only attaches to adjacent pretokens as a
/// single *leading* space of a following word (` ?\p{L}+` and friends), and
/// the only trailing attachments are `[\r\n]*`, which cannot contain a
/// space. Letter/digit runs cannot contain a space either, and the
/// all-whitespace rules (`\s+(?!\S)`, `\s*[\r\n]+`, …) never see a run that
/// crosses the boundary because the preceding byte is alphanumeric. The
/// three ASCII bytes also cannot sit inside a multi-byte UTF-8 character.
///
/// `added_tokens` are the byte sequences matched atomically *before*
/// pretokenization (see `Tokenizer::encode_with_added_tokens`), each paired
/// with its `rstrip` flag; a candidate boundary is rejected when an
/// occurrence of one straddles it, since the halves would otherwise be
/// BPE-encoded as plain text. Only tokens that contain a space can ever
/// straddle a boundary (every boundary sits on a space byte), so for typical
/// vocabularies the check costs nothing. If no occurrence crosses a
/// boundary, greedy leftmost-longest matching restarted there reproduces the
/// single-pass matches: the matcher's only state is its scan position, and
/// no match can carry it across the boundary.
///
/// An `rstrip` token absorbs the whitespace after its match, so a boundary
/// is also rejected when such an occurrence ends exactly at it — the space
/// opening the next chunk would encode as plain text instead of being
/// absorbed. (`lstrip` needs no counterpart: a boundary space preceding a
/// match lands at the start of the next chunk and is trimmed identically
/// there, and a boundary can never sit inside a longer whitespace run
/// because the byte before it must be alphanumeric.)
// ---------------------------------------------------------------------------
// Parallel pretokenization with document splitting
// ---------------------------------------------------------------------------