Vocabulary Extension: Llama-3 Tokenizer'a 8K TR Token Ekle (Embedding Init Stratejileri)
Llama-3 default tokenizer 128K — multilingual ama TR için verim düşük. 'Extension' yaklaşımı: Llama-3 vocab'ına 8K TR-spesifik token ekle, embedding matrix'i 128K→136K büyüt, yeni satırları akıllıca init et (mean-init, SVD-init, byte-decomposition). RTX 4090'da pratik lab + perplexity delta ölçümü.
Şükrü Yusuf KAYA
32 dakikalık okuma
İleri🎯 Hedef
Llama-3'ün 128K multilingual vocab'ı TR'de 3.2 tokens/word. Bu Lab ile 136K vocab → 2.1 tokens/word. Önemli: yeni token'lar için embedding ağırlıkları akıllıca initialize edilmeli, yoksa model bu token'lara 'çöp' atar.
1. Niye Sıfırdan Yeni Tokenizer Değil de Extension?#
Sıfırdan TR-only 50K tokenizer eğitirsen:
- Tüm Llama-3 weights bozulur (mismatched vocab)
- TR-only model — İngilizce capability sıfır
- Pre-training compute büyük
Extension:
- Llama-3 weights korunur (TR + EN capability)
- Sadece TR-spesifik 8K token eklenir
- Yeni token embedding'leri akıllıca init edilirse minimal düzeltme yeter
- Continual pre-train veya doğrudan SFT ile adapte olur
python
# === Llama-3 Tokenizer Extension Lab ===from transformers import AutoTokenizerfrom tokenizers import Tokenizer # 1. Base tokenizer'ı yüklebase = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3.1-8B")print(f"Base vocab size: {base.vocab_size}") # 128256 # 2. TR-spesifik tokenizer'ı yükle (önceki dersten)tr_tok = Tokenizer.from_file("tr_bpe_50k.json")tr_vocab = tr_tok.get_vocab() # {token_str: id} # 3. Aday TR token'ları seç: Llama-3'te olmayan, TR-corpus'ta sıktr_corpus_sample = open("/data/tr-corpus/oscar-tr.txt").read()[:50_000_000] # 50MB sample from collections import Counter# Tokenize sample with TR tokenizertr_tokens_in_corpus = [t for t in tr_tok.encode(tr_corpus_sample).tokens]tr_token_freq = Counter(tr_tokens_in_corpus) # Base tokenizer'ın vocab'ında olmayanları filtrebase_vocab_set = set(base.get_vocab().keys())new_candidates = []for tok, freq in tr_token_freq.most_common(): if tok not in base_vocab_set and freq >= 50: new_candidates.append((tok, freq)) if len(new_candidates) >= 8000: break print(f"Aday TR-spesifik token sayısı: {len(new_candidates)}") # 4. Base tokenizer'a eklenew_tokens = [tok for tok, _ in new_candidates]n_added = base.add_tokens(new_tokens)print(f"Eklenen: {n_added}, Yeni vocab: {len(base)}") # 128256 + 8000 = 136256 # 5. Kaydetbase.save_pretrained("llama3-tr-tok-136k")Llama-3 + 8K TR token extension
python
# === Embedding Init Stratejileri ===import torchfrom transformers import AutoModelForCausalLM, AutoTokenizer ext_tok = AutoTokenizer.from_pretrained("llama3-tr-tok-136k")model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3.1-8B", torch_dtype=torch.bfloat16, device_map="cuda") # Mevcut embedding 128256 satırold_emb = model.get_input_embeddings().weight.dataold_lm_head = model.get_output_embeddings().weight.dataold_vocab = 128256new_vocab = len(ext_tok)print(f"old emb shape: {old_emb.shape}, new vocab: {new_vocab}") # 6. Embedding'i resize etmodel.resize_token_embeddings(new_vocab)new_emb = model.get_input_embeddings().weight.datanew_lm_head = model.get_output_embeddings().weight.data # 7. STRATEJİ A — Mean-init (en basit)mean_vec = old_emb.mean(dim=0, keepdim=True) # [1, hidden]new_emb[old_vocab:] = mean_vec.expand(new_vocab - old_vocab, -1)new_lm_head[old_vocab:] = old_lm_head.mean(dim=0, keepdim=True).expand(new_vocab - old_vocab, -1) # 7b. STRATEJİ B — Byte-decomposition init (en akıllı)# Yeni TR token "şirket" → base tokenizer'da ["ş", "i", "r", "k", "e", "t"] olarak parçalanabilir# Yeni embedding'i = ortalama(parça embedding'leri) yapbase = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3.1-8B")for new_id, tok_str in enumerate(ext_tok.convert_ids_to_tokens(range(old_vocab, new_vocab))): # base tokenizer ile yeni token'ı parçala decomp_ids = base.encode(tok_str.replace("Ġ", " "), add_special_tokens=False) if decomp_ids: new_emb[old_vocab + new_id] = old_emb[decomp_ids].mean(dim=0) new_lm_head[old_vocab + new_id] = old_lm_head[decomp_ids].mean(dim=0) # 7c. STRATEJİ C — SVD-init (research)# Old embedding matrix'in low-rank yaklaşımıyla yeni vektör çıkarU, S, V = torch.svd(old_emb.float())# Yeni embedding'ler için random combination of top-k singular vectorstorch.manual_seed(42)k = 32for new_id in range(old_vocab, new_vocab): coeffs = torch.randn(k) * 0.02 new_emb[new_id] = (U[:, :k] @ (S[:k] * coeffs)).bfloat16() # 8. Savemodel.save_pretrained("llama3-tr-emb-136k")ext_tok.save_pretrained("llama3-tr-emb-136k")embedding init: mean / byte-decomposition / SVD
2. Perplexity Delta Ölçümü#
Hangi init stratejisinin pratik etkisi var? 1000 TR cümle üzerinde perplexity:
import torch from torch.nn.functional import cross_entropy @torch.no_grad() def perplexity(model, tokenizer, text, device="cuda"): enc = tokenizer(text, return_tensors="pt").to(device) out = model(**enc, labels=enc["input_ids"]) return torch.exp(out.loss).item() # Test texts = open("/data/tr-eval-1k.txt").readlines() for init_strategy in ["mean", "byte_decomp", "svd"]: model = AutoModelForCausalLM.from_pretrained(f"llama3-tr-emb-136k-{init_strategy}", torch_dtype=torch.bfloat16, device_map="cuda") ppls = [perplexity(model, ext_tok, t) for t in texts[:100]] print(f"{init_strategy:15s} median PPL: {sorted(ppls)[50]:.2f}")
Tipik sonuç (Llama-3.1-8B + extension, continual pre-train yapmadan):
| Strateji | Median PPL | Yorum |
|---|---|---|
| mean-init | 2843 | tüm yeni token'lar aynı vector → kötü |
| byte-decomp | 38.4 | parça embedding ortalaması, yarı-anlamlı |
| svd-init | 124 | rastgele combination, orta |
| base Llama (no extension) | 18.2 | reference (tüm TR'yi parçalı tokenize ediyor) |
Extension önce perplexity yükseliyor (yeni token'lar henüz öğrenilmedi). Continual pre-train (1-5B token) ile 18-22 PPL'a iniyor → base'i geçiyor.
3. Continual Pre-train Maliyeti (Cookbook Sayıları)#
RTX 4090 + Llama-3.1 8B + 8K extension:
- Hedef: 2B token TR continual pre-train
- Effective batch 64K token (packing + grad-accum)
- 2B / 64K = 31,250 step
- step/s ≈ 1.5 (8B, QLoRA-style adapter only on extension)
- Wall-clock: 31,250 / 1.5 / 3600 = 5.8 saat
- Elektrik maliyet: ~₺10
Cloud (8×H100): ~1 saat → $24.
Cookbook'un kuralı: TR extension yapıyorsan mutlaka 1-2B token continual pre-train. Aksi halde model yeni token'ları yorumlayamaz, downstream SFT'de loss yüksek başlar.
🐛 FMD — 'Extension yaptım, SFT'de loss 4.5'ten başlıyor (normal 2.5)'
Hipotez: Embedding init zayıf, yeni token'lar 'rastgele' vector → SFT gradient bunları çekmeye çalışıyor ama 8B parametrenin geri kalanı zaten kararlı → loss yüksek. Çözüm: continual pre-train 1B token önce, sonra SFT. Veya: byte-decomposition init kullan (mean'den çok daha iyi). Drill: 3 init stratejisiyle SFT loss curve'lerini overlay et.
✅ Teslim
- 8K TR token ekle, 3 init stratejisi dene. 2) Perplexity delta'sını ölç. 3) Byte-decomp init + 500M token continual pre-train sonrası downstream SFT'de loss curve gör. 4) Sonraki ders: 2.3 — Tokenizer Distillation: Çoklu Modeller Arası Mapping.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Part 0 — Engineering Foundations
Fine-Tuning Cookbook'a Hoş Geldin: Sistematik, Stage Taksonomisi ve Reproducibility Kontratı
Öğrenmeye BaşlaPart 0 — Engineering Foundations
Reproducibility Stack: Seeds, cuDNN Flags ve Deterministic CUDA — 'Sende Niye Çalışıyor Bende Çalışmıyor' Sorununu Bitir
Öğrenmeye BaşlaPart 0 — Engineering Foundations