HuggingFace Tokenizers Rust + Production Pipeline: Üretim-Kalite Tokenizer'ı Sıfırdan Eğitmek
HuggingFace tokenizers crate'inin Rust mimarisi, 6 katmanlı pipeline (Normalizer → PreTokenizer → Model → PostProcessor → Decoder → Trainer), tokenizer.json format anatomisi, Türkçe production-grade end-to-end training, Rust internals (parallel processing, SIMD, ahash, mmap), tiktoken/SentencePiece conversion, threading + caching + FFI overhead, benchmarklar.
Şükrü Yusuf KAYA
80 dakikalık okuma
İleri🦀 Rust altyapısının arkasında — tek bir kütüphane, milyarlarca token/saniye
HuggingFace tokenizers, 2020'de Anthony Moi öncülüğünde Rust ile yazıldı. Bugün Python ekosisteminde fiili standart: `transformers`, `datasets`, `text-generation-inference`, `vLLM` — hepsi alttan bu kütüphaneyi kullanıyor. Rust seçimi tesadüf değil: SIMD, lock-free hashmap, memory-mapped I/O ile tek thread 1M token/saniye, 16-core'da 16M+. 80 dakika sonra: 6 katmanlı pipeline'ı (Normalizer → PreTokenizer → Model → PostProcessor → Decoder → Trainer) ezbere çizebilecek, kendi Türkçe tokenizer'ını production-grade eğitebilecek, tokenizer.json'ı satır satır okuyabilecek, FFI overhead'ini optimize edebilecek, tiktoken/SentencePiece'i HF formatına convert edebileceksin. Bu, modern LLM mühendisinin dijital cerrah aletleri.
Ders Haritası (16 Bölüm)#
- HuggingFace ekosistemi — transformers vs tokenizers library ayrımı
- Niye Rust — performans, safety, ekosistem entegrasyonu
- Pipeline mimarisi — 6 katman ve veri akışı
- Normalizers — NFC/NFD/NFKC/NFKD, Lowercase, StripAccents, BertNormalizer, Sequence
- PreTokenizers — Whitespace, ByteLevel, Metaspace, Digits, Split, UnicodeScripts
- Models — BPE, WordPiece, Unigram, WordLevel — algorithm parametreleri
- PostProcessors — TemplateProcessing, BertProcessing, RobertaProcessing, ByteLevel
- Decoders — round-trip lossless decoding
- Trainers — BpeTrainer, WordPieceTrainer, UnigramTrainer config detayları
- End-to-end Türkçe training — corpus → tokenizer.json (production-grade)
- tokenizer.json format — JSON şeması, vocab merge_rules, byte-by-byte
- Loading & saving — pretrained, conversion, custom
- Rust internals — parallel processing, ahash, SIMD, mmap, lock-free design
- Production deployment — caching, threading, FFI overhead, memory profile
- Conversion — tiktoken → HF, SentencePiece → HF, custom formats
- Benchmarks — fertility, throughput, memory, Trendyol-LLM karşılaştırma
1. HuggingFace Ekosistemi — Library Ayrımı#
1.1 `transformers` library (model)#
Python package. PyTorch/JAX/TensorFlow model'lerini load eder. AutoTokenizer sınıfı:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")
Bu fast tokenizer (Rust-backed) veya slow tokenizer (pure Python) döner. Default fast.
1.2 `tokenizers` library (tokenization)#
Ayrı Python package. Rust-backed core. transformers'ın kullandığı motor.
from tokenizers import Tokenizer tokenizer = Tokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")
Direkt erişim — daha az overhead, daha hızlı, training mümkün.
1.3 `tiktoken` library (OpenAI native)#
OpenAI'ın kendi Rust BPE library'si. Modül 6.6'da detay. HuggingFace değil.
1.4 `sentencepiece` library (Google)#
Google'ın C++ library'si. Python binding. Modül 6.5'te detay.
1.5 Tercih kriteri#
| Senaryo | Library |
|---|---|
| HuggingFace model load + tokenize | `transformers.AutoTokenizer` |
| Custom training, advanced pipeline | `tokenizers` direkt |
| OpenAI model token counting | `tiktoken` |
| T5/mT5 multilingual classic | `sentencepiece` |
| Yeni model production | `tokenizers` (modern standard) |
1.6 İndirme & install#
pip install tokenizers # Rust core, fast pip install transformers # Otomatik tokenizers'ı çeker pip install sentencepiece # T5, mT5 için pip install tiktoken # OpenAI için
1.7 Versioning#
- `tokenizers` 0.20+ (2024 ortası): Unigram trainer Rust'a taşındı, %50 hız artışı.
- `tokenizers` 0.21+ (2025): Multimodal token desteği eklendi.
- `transformers` her zaman uyumlu `tokenizers` versiyonu bağımlısı (dependency pin).
2. Niye Rust#
2.1 Performans nümerik gerçeği#
Pure Python BPE encoding: ~10K token/sec single thread. Rust BPE: ~1M token/sec single thread. 100x hızlanma.
Neden:
- Compiler optimization: Rust LLVM ile aggressive optimize
- Type safety: runtime type check yok
- Cache locality: contiguous memory, predictable layout
- SIMD: byte processing'de vectorization (avx2, neon)
- Lock-free hashmap: `ahash` + thread-local lookup
2.2 Memory safety#
C/C++ alternatif olabilirdi. Ama:
- Buffer overflow risk = production crash
- Manual memory management hata-yatkın
- Rust ownership system → derleme zamanı garantiler
SentencePiece (C++) production'da yıllarca sorun yaşadı: rare crash'ler, undefined behavior. HF tokenizers (Rust) baştan beri sağlam.
2.3 Ekosistem#
- pyo3 / maturin: Python binding generator, mükemmel UX
- napi-rs: Node.js binding (HF tokenizers WASM bridge için)
- JNI: Java binding (production servers için)
- C ABI: any language
2.4 Async ready#
Rust tokio runtime ile async tokenization mümkün. Production gRPC server tokenizer'ı paralel batch'lerde async işleyebilir.
2.5 Karşılaştırma: BPE encoding 10K cümle#
Pure Python (Karpathy minbpe): 24.3 sec SentencePiece (C++): 1.4 sec tiktoken (Rust): 0.18 sec tokenizers (Rust): 0.22 sec (HF, biraz fazla overhead)
tiktoken HF tokenizers'tan biraz daha hızlı — daha az feature, daha tight loop. HF tokenizers daha esnek (custom pipeline) ama %20 daha yavaş.
3. 6 Katmanlı Pipeline Mimarisi#
3.1 Veri akışı#
Raw text ↓ Normalizer Normalized text (str) ↓ PreTokenizer List of (str, (start, end)) # Pre-tokens with byte offsets ↓ Model (BPE/WordPiece/Unigram/WordLevel) List of (token_id, (start, end)) # Final tokens ↓ PostProcessor List of (token_id, (start, end)) # With special tokens added ↓ [Decoder for reverse direction] str # Decoded text
3.2 Her katmanın görevi#
Normalizer
Unicode normalization, accent stripping, lowercasing, custom replace. Reversible olmayabilir (lossy).
PreTokenizer
Whitespace/punctuation/byte split. Pre-token'lar arasında boundary çizer. Model bu boundary'leri asla geçemez.
Model
Gerçek tokenization algoritması: BPE merges veya WordPiece longest-match veya Unigram Viterbi. Pre-token içinde subword'lere ayırır.
PostProcessor
Special token ekleme (BOS, EOS, [CLS], [SEP]), template processing, attention mask oluşturma.
Decoder
Token IDs → text reverse mapping. Whitespace handling, byte-level decode.
Trainer
Corpus'tan vocab + merge rules öğrenme. Pipeline'ın dışında çalışır, sonuçları model'e write eder.
3.3 Pipeline immutable mı?#
Hayır. Runtime'da update edebilirsin:
tokenizer.normalizer = NFD() tokenizer.pre_tokenizer = Whitespace() # ...
Ama her değişiklik tokenizer'ı fresh state'e götürür — eski tokenize edilmiş outputs invalid.
3.4 Her katman opsiyonel#
# Minimal tokenizer: sadece Model (WordLevel) tokenizer = Tokenizer(WordLevel(unk_token="[UNK]")) # Normalizer yok, PreTokenizer yok, PostProcessor yok
Ama production-grade için tüm katmanlar tipik.
4. Normalizers — Unicode Hijyeni#
4.1 NFC / NFD / NFKC / NFKD#
Unicode Normalization Forms — same character'ın multiple representations'unu unify eder.
Örnek: "é" karakteri iki şekilde temsil edilebilir:
- U+00E9 (precomposed)
- U+0065 + U+0301 (e + combining acute) — decomposed
Normalizers:
- NFC: Composition. Tek karakter halinde (U+00E9). Production'da en yaygın.
- NFD: Decomposition. Birden çok karakter (U+0065 U+0301). StripAccents'ten önce.
- NFKC: Compatibility composition. "fi" (U+FB01) → "fi". Daha agresif.
- NFKD: Compatibility decomposition.
from tokenizers.normalizers import NFC, NFD, NFKC, NFKD tokenizer.normalizer = NFC()
Türkçe için NFC sağlam tercih. İ (U+0130), ı (U+0131), ğ (U+011F), ş (U+015F), ç (U+00E7), ö (U+00F6), ü (U+00FC) tek-codepoint, NFC bunları korur.
4.2 Lowercase#
from tokenizers.normalizers import Lowercase tokenizer.normalizer = Lowercase()
Türkçe tehlike: "İ" (büyük noktalı İ) lowercase'de Python'da "i̇" (i + combining dot) verir, beklenmedik. Türkçe-aware lowercase için `str.casefold()` veya regex preprocessing. `tokenizers.normalizers.Lowercase()` Unicode default — Türkçe için optimal değil.
4.3 StripAccents#
Accent'ları kaldırır (NFD ile birlikte kullanılır):
from tokenizers.normalizers import NFD, StripAccents, Sequence tokenizer.normalizer = Sequence([NFD(), StripAccents()])
Türkçe TEHLİKE: "şehir" → "sehir" (ş → s, ı/ç/ş/ğ etc. dropped). Türkçe semantiği bozulur, asla kullanma.
4.4 BertNormalizer#
BERT-style: NFD + StripAccents + Lowercase + cleaning. Sequence wrapper:
from tokenizers.normalizers import BertNormalizer tokenizer.normalizer = BertNormalizer( clean_text=True, # control chars temizle handle_chinese_chars=True, # Çinli karakterler arasına space strip_accents=None, # None = lower case için strip, ya da False lowercase=True, )
Türkçe için ne yapmalıyım?
- `strip_accents=False` (Türkçe spesifik karakterleri koru)
- `lowercase=True` veya `False` (cased model istiyorsan False)
- BERT-base-Turkish-cased: `lowercase=False, strip_accents=False`
- BERT-base-Turkish-uncased: `lowercase=True, strip_accents=False`
4.5 Replace#
Regex-based substitution:
from tokenizers.normalizers import Replace tokenizer.normalizer = Replace(pattern=r"\s+", content=" ") # multiple spaces → single
4.6 Sequence (compose)#
Birden fazla normalizer'ı zincirle:
from tokenizers.normalizers import Sequence, NFC, Replace, Lowercase tokenizer.normalizer = Sequence([ NFC(), Replace(r"\s+", " "), Lowercase(), ])
4.7 Strip, Prepend#
from tokenizers.normalizers import Strip, Prepend Strip(left=True, right=True) # whitespace trim Prepend(prepend=" ") # SentencePiece-style ▁ benzeri
4.8 Türkçe production önerisi#
from tokenizers.normalizers import Sequence, NFC, Replace tokenizer.normalizer = Sequence([ NFC(), # Unicode normalize Replace(r"[\u200B-\u200F]", ""), # zero-width chars temizle Replace(r"\s+", " "), # whitespace collapse Strip(), # trim ])
Türkçe karakterler korunur, cleanup yapılır.
5. PreTokenizers — Boundary Çizenler#
5.1 Whitespace#
Whitespace + punctuation boundary'leri:
from tokenizers.pre_tokenizers import Whitespace tokenizer.pre_tokenizer = Whitespace() # "Merhaba, dünya!" → ["Merhaba", ",", "dünya", "!"]
Word-level, punctuation ayrı. BERT/WordPiece için klasik.
5.2 WhitespaceSplit#
Sadece whitespace boundary (punctuation kelime parçası):
from tokenizers.pre_tokenizers import WhitespaceSplit tokenizer.pre_tokenizer = WhitespaceSplit() # "Merhaba, dünya!" → ["Merhaba,", "dünya!"]
5.3 Punctuation#
Sadece punctuation boundary:
from tokenizers.pre_tokenizers import Punctuation tokenizer.pre_tokenizer = Punctuation() # "Merhaba,dünya!" → ["Merhaba", ",", "dünya", "!"]
5.4 ByteLevel — GPT-2/4 stili#
from tokenizers.pre_tokenizers import ByteLevel tokenizer.pre_tokenizer = ByteLevel( add_prefix_space=True, # " Merhaba" tek token (space prefix) use_regex=True, # GPT-2 regex pattern trim_offsets=True, # offset cleanup )
Önemli: ByteLevel pre-tokenizer text'i UTF-8 byte'lara çevirir, sonra GPT-2 regex pattern uygular. Sonuçta her token byte-level → asla UNK yok.
5.5 Metaspace — SentencePiece stili#
from tokenizers.pre_tokenizers import Metaspace tokenizer.pre_tokenizer = Metaspace( replacement="▁", # U+2581 lower one eighth block prepend_scheme="first", # ya da "always" / "never" )
Whitespace'i ▁ ile değiştirir. Modül 6.5'teki SentencePiece davranışı.
5.6 Digits#
Sayıları digit-by-digit veya chunk:
from tokenizers.pre_tokenizers import Digits tokenizer.pre_tokenizer = Digits(individual_digits=True) # "2026 yılı" → ["2", "0", "2", "6", " yılı"]
Math reasoning model'leri için kritik (4-digit aritmetiği token-pattern olarak öğrenmek).
5.7 Split — custom delimiter#
from tokenizers.pre_tokenizers import Split tokenizer.pre_tokenizer = Split( pattern=r"[A-Z][a-z]+", behavior="isolated", # ya da "removed" / "merged_with_previous" / "merged_with_next" invert=False, )
5.8 CharDelimiterSplit#
Belirli char'da split:
CharDelimiterSplit(delimiter="|")
5.9 BertPreTokenizer#
from tokenizers.pre_tokenizers import BertPreTokenizer tokenizer.pre_tokenizer = BertPreTokenizer()
Whitespace + punctuation + cleanup. BERT compat için.
5.10 UnicodeScripts#
Unicode script boundary'leri (Latin, Cyrillic, Arabic, ...):
from tokenizers.pre_tokenizers import UnicodeScripts tokenizer.pre_tokenizer = UnicodeScripts()
Multilingual modelde script boundary'lerini korumak için.
5.11 Sequence (compose)#
from tokenizers.pre_tokenizers import Sequence, Whitespace, Punctuation, Digits tokenizer.pre_tokenizer = Sequence([ Whitespace(), Punctuation(), Digits(individual_digits=True), ])
5.12 Türkçe için önerim#
from tokenizers.pre_tokenizers import ByteLevel tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=True, use_regex=True)
GPT-style byte-level: byte fallback, multilingual ready, modern standard.
6. Models — Algoritma Çekirdeği#
6.1 BPE#
from tokenizers.models import BPE model = BPE( vocab=None, # dict: {token: id} merges=None, # list: [("a", "b"), ...] unk_token="<unk>", byte_fallback=True, # rare chars → bytes (Llama-3 stili) dropout=None, # BPE-dropout (Provilkov 2020) fuse_unk=False, end_of_word_suffix=None, continuing_subword_prefix=None, )
Önemli paramlar:
- byte_fallback: True → asla UNK, rare chars byte-level fallback.
- dropout: 0.0-1.0. Training'te %X merge'i skip (regularization).
- continuing_subword_prefix: "##" (WordPiece-style ama BPE'de) — pre-tokenize word continuation marker.
- end_of_word_suffix: "" (eski BPE word boundary marker).
6.2 WordPiece#
from tokenizers.models import WordPiece model = WordPiece( vocab=None, unk_token="[UNK]", max_input_chars_per_word=100, # uzun kelimeler için limit continuing_subword_prefix="##", )
Detay Modül 6.4'te.
6.3 Unigram#
from tokenizers.models import Unigram model = Unigram( vocab=None, # list: [(token, log_prob), ...] unk_id=None, byte_fallback=False, )
Detay Modül 6.5'te.
6.4 WordLevel — basit#
from tokenizers.models import WordLevel model = WordLevel( vocab=None, # dict unk_token="<unk>", )
Subword yok — tam kelimeler tek token. Vocab kelime listesi. Sadece pre-tokenizer'ın kestiği boundary'leri ID'ye çevirir. Modern LLM için pratik değil (vocab patlar).
6.5 Algorithm seçim matrisi#
| Algoritma | Avantaj | Dezavantaj | Kim kullanıyor |
|---|---|---|---|
| BPE | Basit, hızlı, byte-fallback | OOV risk (byte_fallback olmadan) | GPT-2/3/4, Llama-3, Mistral |
| WordPiece | Likelihood-based, morfoloji | UNK risk, byte fallback yok | BERT, DistilBERT |
| Unigram | Probabilistic, subword regularization | Training yavaş | T5, mT5, ALBERT, XLNet |
| WordLevel | Trivial | Vocab patlaması | Nadir |
6.6 Türkçe için tercih#
Modern: BPE + byte_fallback=True. Llama-3 pattern. Türkçe morphology iyi yakalanır, byte-level coverage.
7. PostProcessors — Special Token Enjeksiyonu#
7.1 TemplateProcessing#
En esnek. "single" ve "pair" template:
from tokenizers.processors import TemplateProcessing tokenizer.post_processor = TemplateProcessing( single="[CLS] $A [SEP]", pair="[CLS] $A [SEP] $B:1 [SEP]:1", special_tokens=[ ("[CLS]", tokenizer.token_to_id("[CLS]")), ("[SEP]", tokenizer.token_to_id("[SEP]")), ], )
Syntax:
- `A\`, \`B`: input sequence A ve B (pair tasks için)
- `:0`, `:1`: token_type_id (BERT segment ID)
- `[CLS]`, `[SEP]`: literal special tokenlar
7.2 BertProcessing — shortcut#
from tokenizers.processors import BertProcessing tokenizer.post_processor = BertProcessing( sep=("[SEP]", tokenizer.token_to_id("[SEP]")), cls=("[CLS]", tokenizer.token_to_id("[CLS]")), )
7.3 RobertaProcessing#
RoBERTa BPE için:
from tokenizers.processors import RobertaProcessing tokenizer.post_processor = RobertaProcessing( sep=("</s>", tokenizer.token_to_id("</s>")), cls=("<s>", tokenizer.token_to_id("<s>")), add_prefix_space=True, )
7.4 ByteLevel#
from tokenizers.processors import ByteLevel tokenizer.post_processor = ByteLevel(trim_offsets=True)
ByteLevel pre-tokenizer ile uyumlu offset cleanup.
7.5 Custom chat template — direkt PostProcessor değil#
Chat templates Modül 6.7'de gördüğümüz Jinja2 templates. PostProcessor değil — `tokenizer.chat_template` field'ı.
8. Decoders — Round-Trip Lossless#
8.1 BPE Decoder#
from tokenizers.decoders import BPEDecoder tokenizer.decoder = BPEDecoder()
8.2 WordPiece Decoder#
from tokenizers.decoders import WordPiece tokenizer.decoder = WordPiece(prefix="##", cleanup=True)
8.3 ByteLevel Decoder#
from tokenizers.decoders import ByteLevel tokenizer.decoder = ByteLevel()
8.4 Metaspace Decoder#
from tokenizers.decoders import Metaspace tokenizer.decoder = Metaspace(replacement="▁")
8.5 Lossless requirement#
Ideal decoder: `decode(encode(x)) == x` her zaman. Pratik:
- ByteLevel BPE: ✅ tam lossless
- WordPiece: ⚠️ "##" prefix bilgi taşır ama whitespace handling değişebilir
- Metaspace: ✅ ▁ → space tam reversible
- BPE classic: ⚠️ word boundary marker depending on training
8.6 Strip, Replace, Sequence#
from tokenizers.decoders import Strip, Replace, Sequence Sequence([ByteLevel(), Replace("▁", " "), Strip(content=" ", left=1)])
9. Trainers — Vocab Öğrenme#
9.1 BpeTrainer#
from tokenizers.trainers import BpeTrainer trainer = BpeTrainer( vocab_size=32000, min_frequency=2, # min pair count to consider special_tokens=["<pad>", "<unk>", "<s>", "</s>", "<|im_start|>", "<|im_end|>"], initial_alphabet=ByteLevel.alphabet(), # tüm 256 byte continuing_subword_prefix=None, end_of_word_suffix=None, show_progress=True, max_token_length=None, )
Önemli paramlar:
- vocab_size: target. Türkçe-only 32K-50K, multilingual 100K+.
- min_frequency: 2 default. Küçük corpus için 1.
- special_tokens: vocab başına eklenir, ID 0,1,2,... reserved.
- initial_alphabet: base chars/bytes. ByteLevel.alphabet() → 256 byte garanti.
9.2 WordPieceTrainer#
from tokenizers.trainers import WordPieceTrainer trainer = WordPieceTrainer( vocab_size=32000, min_frequency=2, special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"], continuing_subword_prefix="##", end_of_word_suffix=None, show_progress=True, )
9.3 UnigramTrainer#
from tokenizers.trainers import UnigramTrainer trainer = UnigramTrainer( vocab_size=32000, special_tokens=["<pad>", "<unk>", "<s>", "</s>"], initial_alphabet=[], shrinking_factor=0.75, # round başına %25 küçült unk_token="<unk>", max_piece_length=16, n_sub_iterations=2, seed_sentencepiece_size=1_000_000, show_progress=True, )
Unigram detay Modül 6.5'te. Bu trainer Kudo 2018 algorithm'ini Rust'ta implement eder (v0.20+).
9.4 WordLevelTrainer#
from tokenizers.trainers import WordLevelTrainer trainer = WordLevelTrainer( vocab_size=10000, min_frequency=5, special_tokens=["<unk>"], )
9.5 Trainer çalıştırma#
files = ["corpus/wiki-tr.txt", "corpus/news-tr.txt"] tokenizer.train(files, trainer)
Veya Python iterator'dan:
def batch_iterator(): for batch in dataset.iter(batch_size=1000): yield batch["text"] tokenizer.train_from_iterator(batch_iterator(), trainer=trainer, length=len(dataset))
9.6 Threading#
HF tokenizers default tüm CPU core'ları kullanır:
import os os.environ["RAYON_NUM_THREADS"] = "16" # explicit thread sayısı
Rayon = Rust'ın parallel iterator library'si.
10. End-to-End Türkçe Tokenizer Training (Production-Grade)#
Tüm pipeline'ı birleştiren tam training script. Sonuç: `turk-bpe-32k.json` (~2 MB).
Adım adım kodun:#
from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.normalizers import Sequence, NFC, Replace, Strip from tokenizers.pre_tokenizers import ByteLevel from tokenizers.processors import ByteLevel as ByteLevelProcessor from tokenizers.decoders import ByteLevel as ByteLevelDecoder from tokenizers.trainers import BpeTrainer # 1. Tokenizer init tokenizer = Tokenizer(BPE(unk_token=None, byte_fallback=True)) # 2. Normalizer tokenizer.normalizer = Sequence([ NFC(), Replace(r"[\u200B-\u200F\uFEFF]", ""), # zero-width chars Replace(r"\s+", " "), # whitespace collapse Strip(), ]) # 3. Pre-tokenizer tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=True, use_regex=True) # 4. Post-processor tokenizer.post_processor = ByteLevelProcessor(trim_offsets=True) # 5. Decoder tokenizer.decoder = ByteLevelDecoder() # 6. Trainer config trainer = BpeTrainer( vocab_size=32000, min_frequency=2, special_tokens=[ "<|endoftext|>", "<|pad|>", "<|im_start|>", "<|im_end|>", "<|user|>", "<|assistant|>", "<|system|>", "<|tool|>", ], initial_alphabet=ByteLevel.alphabet(), show_progress=True, max_token_length=24, # max sub-word length ) # 7. Train files = [ "corpus/wiki-tr.txt", "corpus/news-tr.txt", "corpus/oscar-tr.txt", "corpus/literature-tr.txt", ] tokenizer.train(files, trainer) # 8. Save tokenizer.save("turk-bpe-32k.json") # 9. Test result = tokenizer.encode("İstanbul Boğazı'nda balıkçı tekneleri sallanıyor.") print("Tokens:", result.tokens) print("IDs:", result.ids) print("Count:", len(result.ids))
Training time benchmark#
10 GB Türkçe corpus, 16-core EPYC:
- Step 1-5 (setup): <1 sec
- Step 7 (BPE training): ~25 minutes
- Total: ~25 minutes
Memory profile#
- Corpus mmap: ~10 GB (virtual, disk-backed)
- BPE training peak: ~12 GB RAM
- Output tokenizer.json: ~2 MB
Production önerileri#
- Corpus diversity: Wikipedia + OSCAR + news + literature + code mix
- Cleaning: HTML strip, deduplicate, language detect
- Min frequency: 2 ideal, 1 only for very small corpus
- Special tokens: chat format'ın hepsi vocab'da olsun
- max_token_length: 16-24 sweet spot
- initial_alphabet: byte-level garanti için ByteLevel.alphabet()
11. tokenizer.json Format Anatomisi#
11.1 Üst-seviye yapı#
{ "version": "1.0", "truncation": null, "padding": null, "added_tokens": [...], "normalizer": {...}, "pre_tokenizer": {...}, "post_processor": {...}, "decoder": {...}, "model": {...} }
11.2 added_tokens#
Special tokenlar:
"added_tokens": [ { "id": 0, "content": "<|endoftext|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true }, { "id": 1, "content": "<|im_start|>", ... } ]
11.3 normalizer#
"normalizer": { "type": "Sequence", "normalizers": [ {"type": "NFC"}, {"type": "Replace", "pattern": {"Regex": "\\s+"}, "content": " "} ] }
11.4 pre_tokenizer#
"pre_tokenizer": { "type": "ByteLevel", "add_prefix_space": true, "trim_offsets": true, "use_regex": true }
11.5 model (BPE)#
"model": { "type": "BPE", "dropout": null, "unk_token": null, "continuing_subword_prefix": null, "end_of_word_suffix": null, "fuse_unk": false, "byte_fallback": true, "vocab": { "!": 0, "\"": 1, ... "Ġmerhaba": 4523, "Ġdünya": 6711, ... }, "merges": [ "Ġ d", "e r", "i n", ... ] }
11.6 vocab byte encoding#
"Ġ" U+0120 — GPT-2 byte encoding'inde space byte (0x20). Bu, "Ġmerhaba" aslında " merhaba" (space-prefixed). Modül 6.6'da detay.
11.7 merges sırası kritik#
BPE merges sırayla uygulanır. İlk merge en sık pair, son merge en nadir. İşin matematiği Modül 6.2'de.
11.8 Dosya boyutu beklenen#
- 32K vocab BPE: ~2-3 MB
- 100K vocab: ~6-8 MB
- 200K vocab (o200k): ~12 MB
11.9 Sürüm uyumluluğu#
`"version": "1.0"` — tokenizers 0.10+ format. `"version": "0.x"` legacy. Yeni training her zaman "1.0".
12. Loading & Saving — Format Galaxies#
12.1 HF native#
# Save tokenizer.save("turk-bpe-32k.json") # Load from tokenizers import Tokenizer tokenizer = Tokenizer.from_file("turk-bpe-32k.json")
12.2 HF Hub'dan pretrained#
tokenizer = Tokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
Oto-download, cache (default `~/.cache/huggingface`).
12.3 transformers AutoTokenizer ile uyumlu#
from transformers import PreTrainedTokenizerFast tokenizer = PreTrainedTokenizerFast(tokenizer_file="turk-bpe-32k.json") tokenizer.save_pretrained("./my-tokenizer") # Çıktı: my-tokenizer/tokenizer.json + tokenizer_config.json + special_tokens_map.json
12.4 Legacy BERT format (vocab.txt)#
from tokenizers import BertWordPieceTokenizer tokenizer = BertWordPieceTokenizer("vocab.txt", lowercase=False)
12.5 Legacy SentencePiece (.model)#
# transformers auto-converts SP .model to fast tokenizer from transformers import T5Tokenizer tokenizer = T5Tokenizer.from_pretrained("google/mt5-base") fast = tokenizer.train_new_from_iterator(...)
Manuel conversion: `convert_slow_tokenizer` utility.
12.6 tiktoken → HF conversion#
Direct değil. Workaround:
import tiktoken enc = tiktoken.get_encoding("cl100k_base") # 1. Vocab extract vocab = {} for i in range(enc.n_vocab): try: token = enc.decode_single_token_bytes(i).decode("utf-8", errors="replace") vocab[token] = i except: pass # 2. HF BPE oluştur (merges yok!) from tokenizers import Tokenizer from tokenizers.models import BPE tokenizer = Tokenizer(BPE(vocab=vocab, merges=[], byte_fallback=True)) # Note: merges yok → bu sadece encode yapamayan dummy. Tam conversion için merge inferring gerekir.
Tam tiktoken → HF için `hf-internal-testing/llama-tokenizer` benzeri community converter'ları var.
13. Rust Internals — Performansın Kaynakları#
13.1 Parallel processing (rayon)#
Batch encode paralel:
// Rust internals (simplified) use rayon::prelude::*; pub fn encode_batch(&self, inputs: Vec<&str>) -> Vec<Encoding> { inputs.par_iter() .map(|input| self.encode(input)) .collect() }
Python call:
results = tokenizer.encode_batch(["cümle 1", "cümle 2", "cümle 3"])
N core'da N kat hızlanma (embarrassingly parallel).
13.2 ahash — fast hashmap#
Default Rust HashMap SipHash kullanır (DoS resistance, ama yavaş). HF tokenizers `ahash` library:
- 2-3x daha hızlı lookup
- Vocab lookup'da kritik (her char için)
- Production-tested, no DoS issue (tokenizer input trusted)
13.3 SIMD vectorization#
Byte-level operations Rust auto-vectorized:
- AVX2 (x86_64): 32 byte at once
- NEON (ARM, Apple Silicon): 16 byte at once
- UTF-8 validation, byte-to-unicode mapping, BPE merge application — hepsi SIMD-friendly
13.4 mmap (memory-mapped I/O)#
Corpus dosyaları `mmap` ile okunur:
- OS page cache leverage
- Disk I/O bandwidth saturate (NVMe için ~3 GB/sec)
- RAM kullanımı virtual (OS yönetir)
13.5 Lock-free design#
Training sırasında token frequency counts ConcurrentHashMap kullanmıyor — her thread kendi local count'unu tutar, sonunda merge. Lock contention sıfır.
13.6 Zero-copy strings#
Rust `str` ve `&str` zero-copy reference. UTF-8 byte slice'lar copy edilmeden işlenir.
13.7 Profile detayı (encode 1M cümle)#
Normalizer: 12% time PreTokenizer: 8% BPE lookup: 65% time (vocab dictionary lookup) PostProcessor: 3% Overhead: 12% (FFI, allocation, etc.)
Bottleneck: BPE lookup. ahash + cache-friendly vocab layout ile optimize.
14. Production Deployment — Pratik Konular#
14.1 Threading#
Default: tüm core'lar. Container'da CPU limit'i varsa explicit ayarla:
import os os.environ["RAYON_NUM_THREADS"] = "4" # Kubernetes pod 4 CPU
14.2 Caching#
Tokenize sonuçları idempotent — cache safe. Production'da:
- Redis cache (hash(text) → tokens)
- LRU local cache (Python `functools.lru_cache`)
- Database column (her satır için precomputed tokens)
Genel kural: aynı text'i 2x+ tokenize ediyorsan cache.
14.3 FFI overhead#
Python ↔ Rust FFI overhead ~3-10 microsecond per call. Küçük input'lar için bu önemli:
# YAVAŞ — 1M call x 5 μs = 5 sec overhead for sentence in corpus: tokens = tokenizer.encode(sentence) # HIZLI — 1 call, batch processing tokens_batch = tokenizer.encode_batch(corpus)
10x-100x hızlanma sadece batch ile.
14.4 Memory profile#
Tokenizer load: ~50-100 MB (vocab + merges).
Encoding overhead: ~1 KB per encoding (token IDs + offsets).
Production server: typical 200 MB RAM tokenizer için.
14.5 Multi-process worker pattern#
from multiprocessing import Pool def worker(text): return tokenizer.encode(text) with Pool(8) as p: results = p.map(worker, corpus)
Ama HF tokenizers zaten Rust thread-pool kullanıyor — `encode_batch` çoğu durumda yeterli, Python multiprocessing gereksiz.
14.6 vLLM / TGI integration#
Production inference server'lar (vLLM, TGI, SGLang) HF tokenizer'ı doğrudan kullanır. Custom pipeline gerekmiyor.
14.7 Streaming (real-time)#
LLM generation sırasında token-by-token decode:
for token_id in model_output_stream: text = tokenizer.decode([token_id]) print(text, end="", flush=True)
ByteLevel BPE'de partial decode tehlikesi: byte boundary'sinden ortadan kesilmiş UTF-8 char'lar. Çözüm: `skip_special_tokens=True` + ByteLevel decoder'ın native partial UTF-8 handling'ı.
15. Conversion — Diğer Formatlardan HF'ye#
15.1 tiktoken → HF#
Manuel inferred merges (complex). Community tool: `hf-internal/tiktoken-to-hf`.
15.2 SentencePiece → HF#
Kolay (transformers auto-conversion):
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("google/mt5-base") # Internal: SentencePiece .model → HF tokenizer.json tokenizer.save_pretrained("./mt5-tokenizer")
15.3 OpenAI'dan kendi format'a#
OpenAI tokenizer indirilemez (closed). Conversion mümkün değil.
15.4 BERT vocab.txt → HF#
from tokenizers import BertWordPieceTokenizer tokenizer = BertWordPieceTokenizer("vocab.txt", lowercase=False) tokenizer.save("bert-tr.json")
15.5 GPT-2 encoder.json + vocab.bpe → HF#
from tokenizers import Tokenizer, models, pre_tokenizers, decoders, processors import json with open("encoder.json") as f: vocab = json.load(f) with open("vocab.bpe") as f: merges = [tuple(line.split()) for line in f.readlines()[1:] if line.strip()] tokenizer = Tokenizer(models.BPE(vocab=vocab, merges=merges)) tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) tokenizer.decoder = decoders.ByteLevel() tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) tokenizer.save("gpt2-hf.json")
16. Benchmarks — Türkçe Karşılaştırma#
16.1 Fertility (token/word) — 1 sayfa Türkçe metin (300 kelime)#
| Tokenizer | Vocab | Tokens | token/word |
|---|---|---|---|
| GPT-2 (r50k) | 50K | ~720 | 2.40 |
| GPT-4 (cl100k) | 100K | ~510 | 1.70 |
| GPT-4o (o200k) | 200K | ~430 | 1.43 |
| Llama-3 | 128K | ~440 | 1.47 |
| TurkBPE-32k (custom) | 32K | ~360 | 1.20 |
| Trendyol-LLM | 32K | ~360 | 1.20 |
| BERT-base-Turkish | 32K | ~465 | 1.55 |
Custom 32K Türkçe-only BPE → Llama-3'e göre %18 daha az token.
16.2 Encoding throughput (single thread)#
| Library | Implementation | Tokens/sec |
|---|---|---|
| Karpathy minbpe | Pure Python | ~10K |
| sentencepiece | C++ | ~700K |
| HF tokenizers | Rust | ~900K |
| tiktoken | Rust | ~1.1M |
16.3 Batch encoding (16 thread)#
HF tokenizers 16-core'da ~14M token/sec (near-linear scaling).
16.4 Memory#
| Vocab boyutu | RAM |
|---|---|
| 32K | ~50 MB |
| 100K | ~150 MB |
| 200K | ~280 MB |
16.5 Training time (10 GB Türkçe corpus, 16 core)#
- BPE: ~25 min
- WordPiece: ~30 min
- Unigram: ~90 min (slower algorithm)
16.6 İndirme & startup time#
Production load:
- Tokenizer.from_file(): ~50-200 ms (vocab parsing)
- from_pretrained(): ilk kez ~3 sec (network + parse), cached ~100 ms
Egzersizler#
Egzersiz 1#
Türkçe corpus 5 GB için BPE training config: `vocab_size`, `min_frequency`, `initial_alphabet`, `max_token_length`, `special_tokens` ne olmalı? Sebep ile.
Egzersiz 2#
Normalizer pipeline: `Sequence([NFD(), StripAccents(), Lowercase()])` Türkçe için niye kötü? Doğru pipeline ne?
Egzersiz 3#
`add_prefix_space=True` ne anlama gelir? GPT-2 ByteLevel pre-tokenizer'da neden var?
Egzersiz 4#
tokenizer.json'da `"merges"` listesi sıralı. Sırasının önemi ne? İlk merge ile son merge arasında fark?
Egzersiz 5#
Production'da `encode()` 1M kez çağrılıyor, %FFI overhead ne kadar? `encode_batch()` ile karşılaştırma yap.
Egzersiz 6#
BpeTrainer'da `min_frequency=10` set ettin. Sonuç: vocab size hedeften küçük geldi. Niye? Nasıl düzeltirsin?
Egzersiz 7#
SentencePiece .model dosyasını HF tokenizer.json'a manuel convert et. Adımları yaz.
Egzersiz 8#
ByteLevel decoder'da "partial UTF-8" problemi ne? Streaming generation'da nasıl ortaya çıkar?
Egzersiz 9#
Custom tokenizer 32K vocab'a sahip. RAM kullanımı ölçüldüğünde 500 MB. Beklentin 50 MB civarı idi. Olası sebepler?
Egzersiz 10#
`tokenizer.train_from_iterator()` ile streaming corpus'tan training. Bellek dağılımı nasıl?
✅ Ders 6.8 Özeti — Production Tokenizer Mühendisliği
HuggingFace tokenizers Rust ile yazılmış, modern fiili standard. 6 katmanlı pipeline: Normalizer → PreTokenizer → Model → PostProcessor → Decoder → Trainer. Türkçe için NFC + ByteLevel + BPE + byte_fallback önerim. `tokenizer.json` format-portable. Rust internals: rayon parallel, ahash, SIMD, mmap → 1M token/sec single thread. Production: `encode_batch`, threading, caching, partial UTF-8 handling. tiktoken/SentencePiece conversion mümkün. Custom Türkçe 32K BPE → Llama-3'ten %18 daha az token. Modül 6.9'da tokenizer evaluation metriklerine geçeceğiz.
Sıradaki Ders: Tokenizer Evaluation#
Modül 6.9'da: fertility, compression ratio, OOV rate, perplexity downstream impact, cross-lingual fertility, A/B testing, bits-per-character information theory, tokenization 'tax' in cost terms, capstone evaluation framework — TurkTokenizer-tr için ölçüm araçları.
Sık Sorulan Sorular
İkisi de aynı Rust core'u kullanır. AutoTokenizer transformers integration için daha kolay (model + tokenizer birlikte). tokenizers library training + custom pipeline için daha güçlü. Production: model serving ise AutoTokenizer, custom training ise tokenizers direkt.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 0: Kurs Çerçevesi ve Atölye Kurulumu
LLM Engineer Kimdir? Junior'dan Staff'a Yapay Zekâ Mühendisliği Kariyer Haritası
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu
Kurs Felsefesi: Neden Bu Yol, Neden Bu Sıra — 8 Aylık Müfredatın İskeleti
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu