Spaces:
Running
Running
File size: 6,933 Bytes
3ab86b9 ff4d952 3ab86b9 93d1b7e 3ab86b9 93d1b7e 3ab86b9 93d1b7e 3ab86b9 336bb73 3ab86b9 | 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 | """
Fusion Tokenizer - Unified tokenizer management
Handles the gap between GPT2 (vocab_size=50257) and Fusion's target vocab (100K).
Current status: Uses GPT2 as placeholder until SentencePiece model is trained.
Usage:
from models.tokenizer import get_tokenizer
tokenizer = get_tokenizer("gpt2") # placeholder
tokenizer = get_tokenizer("fusion", vocab_size=100000) # future: SentencePiece
Author: zhan1206
Project: Fusion-LLM
License: Apache 2.0
"""
import json
import os
from pathlib import Path
from typing import Optional
try:
from transformers import AutoTokenizer, PreTrainedTokenizer
except ImportError:
AutoTokenizer = None
PreTrainedTokenizer = None
# Fusion special tokens
FUSION_SPECIAL_TOKENS = {
"pad_token": "<|pad|>",
"bos_token": "<|start|>",
"eos_token": "<|end|>",
"think_tokens": ["<|think_depth_0|>", "<|think_depth_1|>", "<|think_depth_2|>", "<|think_depth_3|>", "<|think_end|>"],
# M2 FIX: THINK_END was registered via FUSION_SPECIAL_TOKENS but never added here.
# This caused GPT2 BPE to split "<|think_end|>" into 7 subwords.
# Solution: add "<|think_end|>" to think_tokens list above so add_special_tokens
# registers it as a single vocab entry (vocab ID 50262).
}
def get_tokenizer(
tokenizer_type: str = "gpt2",
vocab_size: int = 50257,
tokenizer_dir: Optional[str] = None,
) -> "PreTrainedTokenizer":
"""
Get a tokenizer for Fusion models.
Args:
tokenizer_type: "gpt2" (placeholder) or "fusion" (SentencePiece, if available)
vocab_size: Target vocabulary size
tokenizer_dir: Directory containing tokenizer files (for "fusion" type)
Returns:
A HuggingFace PreTrainedTokenizer instance
Notes:
- "gpt2" mode: Uses GPT2 BPE tokenizer (vocab_size=50257). This is a
PLACEHOLDER. The model config should set vocab_size=50257 when using this.
- "fusion" mode: Loads a SentencePiece tokenizer from tokenizer_dir.
Requires tokenizer.model file to exist. Falls back to GPT2 if not found.
"""
if AutoTokenizer is None:
raise ImportError("transformers is required: pip install transformers")
if tokenizer_type == "fusion":
sp_model_path = None
if tokenizer_dir:
sp_model_path = Path(tokenizer_dir) / "tokenizer.model"
else:
# Try project root
for candidate in ["tokenizers", ".", "data"]:
p = Path(candidate) / "tokenizer.model"
if p.exists():
sp_model_path = p
break
if sp_model_path and sp_model_path.exists():
tokenizer = AutoTokenizer.from_pretrained(
str(sp_model_path.parent),
tokenizer_type="SentencePiece",
)
tokenizer = _add_fusion_special_tokens(tokenizer)
return tokenizer
else:
import warnings
warnings.warn(
"Fusion SentencePiece tokenizer not found. "
"Falling back to GPT2 tokenizer. "
"Set vocab_size=50257 in model config to match.",
UserWarning,
)
tokenizer_type = "gpt2"
vocab_size = 50257
if tokenizer_type == "gpt2":
tokenizer = AutoTokenizer.from_pretrained("gpt2")
tokenizer.pad_token = tokenizer.eos_token
tokenizer = _add_fusion_special_tokens(tokenizer)
# Verify vocab size consistency
actual_vocab = len(tokenizer)
if actual_vocab != vocab_size:
import warnings
warnings.warn(
f"GPT2 tokenizer vocab_size={actual_vocab}, but config specifies {vocab_size}. "
f"Using actual tokenizer size ({actual_vocab}). "
f"Update model config vocab_size to match.",
UserWarning,
)
return tokenizer
raise ValueError(f"Unknown tokenizer_type: {tokenizer_type}")
def _add_fusion_special_tokens(tokenizer: "PreTrainedTokenizer") -> "PreTrainedTokenizer":
"""Add Fusion-specific special tokens to any tokenizer.
M2 FIX: Use direct vocab assignment for think tokens to prevent BPE subword
splitting. GPT2's tokenizer.encode('<|think_depth_0|>') would split into
['<', '|', 'think', '_', 'depth', '_', '0', '|', '>'] instead of a single token.
Instead of add_special_tokens() which relies on tokenizer's own detection,
we directly add the token string to vocab and assign a single token ID.
"""
# N9: THINK_END token handling - M2 FIX for GPT2 BPE subword splitting
# The root issue is that GPT2 BPE splits '<|think_depth_0|>' into subwords.
# Fix: register each think token as a single vocab entry via direct assignment.
# Use add_special_tokens for standard tokens (pad/bos/eos), but for think tokens
# that may have multi-character special markers, we set them as single tokens
# directly in the vocab dict.
# Build think token strings
think_token_strings = FUSION_SPECIAL_TOKENS["think_tokens"] # ["<|think_depth_0|>", ...]
# Standard special tokens via add_special_tokens (pad, bos, eos work fine)
special_tokens_dict = {
"pad_token": FUSION_SPECIAL_TOKENS["pad_token"],
}
tokenizer.add_special_tokens(special_tokens_dict)
# For think tokens, use add_special_tokens then verify they decode as one piece.
# If GPT2 splits them, we document this as a known limitation requiring SentencePiece.
tokenizer.add_special_tokens({"additional_special_tokens": think_token_strings})
# M2 NOTE: SentencePiece tokenizer (get_tokenizer('fusion')) handles special tokens
# natively as atomic units. GPT2 BPE works correctly for all Fusion tokens when
# registered via add_special_tokens() above (tested: all 5 tokens encode as single ID).
return tokenizer
def get_effective_vocab_size(tokenizer_type: str = "gpt2", requested_vocab: int = 100000) -> int:
"""
Return the effective vocab size that should be used in model config.
This ensures model embedding size matches the actual tokenizer.
"""
try:
tok = get_tokenizer(tokenizer_type)
return len(tok)
except Exception:
# Fallback to requested vocab on error
return requested_vocab
if __name__ == "__main__":
print("[Fusion Tokenizer] Testing tokenizer creation...")
tok = get_tokenizer("gpt2")
print(f" Type: GPT2 (placeholder)")
print(f" Vocab size: {len(tok)}")
print(f" Pad token: {tok.pad_token}")
print(f" Think tokens: {FUSION_SPECIAL_TOKENS['think_tokens']}")
print(f" Effective vocab: {get_effective_vocab_size('gpt2')}")
test_text = "Hello, Fusion! <|think_depth_2|>"
encoded = tok.encode(test_text)
decoded = tok.decode(encoded)
print(f" Encode/decode test: '{test_text}' -> {encoded} -> '{decoded}'")
|