Spaces:
Running
Running
zhan1206 commited on
Commit ·
bae07a4
1
Parent(s): ff4d952
fix: v5 remove empty tokenizer.json, unify think tokens, integrate tokenizer into training pipeline
Browse files- README.md +1 -1
- configs/fusion-mini-config.json +3 -0
- inference/dashboard.py +3 -3
- inference/ollama_deploy.py +7 -7
- inference/ollama_deploy_v2.py +7 -7
- models/thinking_dial.py +3 -3
- scripts/fix_think_tokens.py +57 -0
- tests/run_tests.py +2 -2
- tokenizer.json +0 -40
- tokenizer_config.json +0 -11
- train/dpo_finetune.py +1 -1
- train/full_finetune.py +1 -4
- train/lora_finetune.py +12 -15
- train/train_mini.py +22 -4
README.md
CHANGED
|
@@ -79,7 +79,7 @@ python train/full_finetune.py --model_size 8B
|
|
| 79 |
```python
|
| 80 |
from models.fusion_model import FusionModel, FusionConfig
|
| 81 |
|
| 82 |
-
config = FusionConfig(vocab_size=10000, hidden_size=256,
|
| 83 |
model = FusionModel(config)
|
| 84 |
model.eval()
|
| 85 |
|
|
|
|
| 79 |
```python
|
| 80 |
from models.fusion_model import FusionModel, FusionConfig
|
| 81 |
|
| 82 |
+
config = FusionConfig(vocab_size=10000, hidden_size=256, num_hidden_layers=2)
|
| 83 |
model = FusionModel(config)
|
| 84 |
model.eval()
|
| 85 |
|
configs/fusion-mini-config.json
CHANGED
|
@@ -24,6 +24,9 @@
|
|
| 24 |
"rms_norm_eps": 1e-5,
|
| 25 |
"rope_theta": 10000.0,
|
| 26 |
"tie_word_embeddings": false,
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
"torch_dtype": "float32",
|
| 29 |
"transformers_version": "4.36.0",
|
|
|
|
| 24 |
"rms_norm_eps": 1e-5,
|
| 25 |
"rope_theta": 10000.0,
|
| 26 |
"tie_word_embeddings": false,
|
| 27 |
+
"enable_thinking_dial": true,
|
| 28 |
+
"num_thinking_depths": 4,
|
| 29 |
+
"think_rank": 0,
|
| 30 |
|
| 31 |
"torch_dtype": "float32",
|
| 32 |
"transformers_version": "4.36.0",
|
inference/dashboard.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
Fusion Inference Dashboard - Interactive inference control panel
|
| 4 |
|
|
@@ -128,7 +128,7 @@ class InferenceEngine:
|
|
| 128 |
if hasattr(self.model.config, 'enable_thinking_dial') and self.model.config.enable_thinking_dial:
|
| 129 |
try:
|
| 130 |
from models.thinking_dial import ThinkingDialProcessor
|
| 131 |
-
processor = ThinkingDialProcessor(self._tokenizer or get_tokenizer("
|
| 132 |
self._thinking_depth_token = processor.get_think_token(rank)
|
| 133 |
except Exception:
|
| 134 |
pass
|
|
@@ -138,7 +138,7 @@ class InferenceEngine:
|
|
| 138 |
|
| 139 |
try:
|
| 140 |
from models.tokenizer import get_tokenizer
|
| 141 |
-
self._tokenizer = get_tokenizer("
|
| 142 |
print(f"Tokenizer loaded: vocab_size={self._tokenizer.vocab_size}")
|
| 143 |
except Exception:
|
| 144 |
self._tokenizer = None
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
Fusion Inference Dashboard - Interactive inference control panel
|
| 4 |
|
|
|
|
| 128 |
if hasattr(self.model.config, 'enable_thinking_dial') and self.model.config.enable_thinking_dial:
|
| 129 |
try:
|
| 130 |
from models.thinking_dial import ThinkingDialProcessor
|
| 131 |
+
processor = ThinkingDialProcessor(self._tokenizer or get_tokenizer("fusion"))
|
| 132 |
self._thinking_depth_token = processor.get_think_token(rank)
|
| 133 |
except Exception:
|
| 134 |
pass
|
|
|
|
| 138 |
|
| 139 |
try:
|
| 140 |
from models.tokenizer import get_tokenizer
|
| 141 |
+
self._tokenizer = get_tokenizer("fusion")
|
| 142 |
print(f"Tokenizer loaded: vocab_size={self._tokenizer.vocab_size}")
|
| 143 |
except Exception:
|
| 144 |
self._tokenizer = None
|
inference/ollama_deploy.py
CHANGED
|
@@ -181,8 +181,8 @@ TEMPLATE \"\"\"{{ if .System }}<|im_start|>system
|
|
| 181 |
if thinking_dial:
|
| 182 |
content += f"""
|
| 183 |
# Thinking Dial 示例(训练时注入)
|
| 184 |
-
# <|
|
| 185 |
-
# <|
|
| 186 |
"""
|
| 187 |
|
| 188 |
# 写入文件
|
|
@@ -319,16 +319,16 @@ Fusion 支持动态推理强度控制。在问题前添加控制 token:
|
|
| 319 |
|
| 320 |
```bash
|
| 321 |
# depth=0:直接回答(闲聊、翻译)
|
| 322 |
-
> <|
|
| 323 |
|
| 324 |
# depth=1:简单推理
|
| 325 |
-
> <|
|
| 326 |
|
| 327 |
# depth=2:中等推理
|
| 328 |
-
> <|
|
| 329 |
|
| 330 |
# depth=3:深度推理(思维链)
|
| 331 |
-
> <|
|
| 332 |
```
|
| 333 |
|
| 334 |
## 3. REST API
|
|
@@ -363,7 +363,7 @@ print(response["response"])
|
|
| 363 |
# 带 Thinking Dial
|
| 364 |
response = ollama.generate(
|
| 365 |
model="{model_name}",
|
| 366 |
-
prompt="<|
|
| 367 |
)
|
| 368 |
|
| 369 |
print(response["response"])
|
|
|
|
| 181 |
if thinking_dial:
|
| 182 |
content += f"""
|
| 183 |
# Thinking Dial 示例(训练时注入)
|
| 184 |
+
# <|think_depth_0|> 简单问题,直接回答
|
| 185 |
+
# <|think_depth_3|> 复杂问题,详细推理
|
| 186 |
"""
|
| 187 |
|
| 188 |
# 写入文件
|
|
|
|
| 319 |
|
| 320 |
```bash
|
| 321 |
# depth=0:直接回答(闲聊、翻译)
|
| 322 |
+
> <|think_depth_0|> 今天天气怎么样?
|
| 323 |
|
| 324 |
# depth=1:简单推理
|
| 325 |
+
> <|think_depth_1|> 计算 123 * 456
|
| 326 |
|
| 327 |
# depth=2:中等推理
|
| 328 |
+
> <|think_depth_2|> 证明勾股定理
|
| 329 |
|
| 330 |
# depth=3:深度推理(思维链)
|
| 331 |
+
> <|think_depth_3|> 解决这个算法问题:...
|
| 332 |
```
|
| 333 |
|
| 334 |
## 3. REST API
|
|
|
|
| 363 |
# 带 Thinking Dial
|
| 364 |
response = ollama.generate(
|
| 365 |
model="{model_name}",
|
| 366 |
+
prompt="<|think_depth_2|> 证明勾股定理",
|
| 367 |
)
|
| 368 |
|
| 369 |
print(response["response"])
|
inference/ollama_deploy_v2.py
CHANGED
|
@@ -266,8 +266,8 @@ TEMPLATE \"\"\"{{{{ if .System }}}}<|im_start|>system
|
|
| 266 |
if thinking_dial:
|
| 267 |
content += f"""
|
| 268 |
# Thinking Dial examples (injected during training)
|
| 269 |
-
# <|
|
| 270 |
-
# <|
|
| 271 |
"""
|
| 272 |
|
| 273 |
# Write file
|
|
@@ -416,16 +416,16 @@ Fusion supports dynamic reasoning intensity control. Add control token before qu
|
|
| 416 |
|
| 417 |
```bash
|
| 418 |
# depth=0: direct answer (casual chat, translation)
|
| 419 |
-
> <|
|
| 420 |
|
| 421 |
# depth=1: simple reasoning
|
| 422 |
-
> <|
|
| 423 |
|
| 424 |
# depth=2: medium reasoning
|
| 425 |
-
> <|
|
| 426 |
|
| 427 |
# depth=3: deep reasoning (chain-of-thought)
|
| 428 |
-
> <|
|
| 429 |
```
|
| 430 |
|
| 431 |
## 3. REST API
|
|
@@ -460,7 +460,7 @@ print(response["response"])
|
|
| 460 |
# With Thinking Dial
|
| 461 |
response = ollama.generate(
|
| 462 |
model="{model_name}",
|
| 463 |
-
prompt="<|
|
| 464 |
)
|
| 465 |
|
| 466 |
print(response["response"])
|
|
|
|
| 266 |
if thinking_dial:
|
| 267 |
content += f"""
|
| 268 |
# Thinking Dial examples (injected during training)
|
| 269 |
+
# <|think_depth_0|> Simple question, direct answer
|
| 270 |
+
# <|think_depth_3|> Complex question, detailed reasoning
|
| 271 |
"""
|
| 272 |
|
| 273 |
# Write file
|
|
|
|
| 416 |
|
| 417 |
```bash
|
| 418 |
# depth=0: direct answer (casual chat, translation)
|
| 419 |
+
> <|think_depth_0|> How's the weather today?
|
| 420 |
|
| 421 |
# depth=1: simple reasoning
|
| 422 |
+
> <|think_depth_1|> Calculate 123 * 456
|
| 423 |
|
| 424 |
# depth=2: medium reasoning
|
| 425 |
+
> <|think_depth_2|> Prove Pythagorean theorem
|
| 426 |
|
| 427 |
# depth=3: deep reasoning (chain-of-thought)
|
| 428 |
+
> <|think_depth_3|> Solve this algorithm problem: ...
|
| 429 |
```
|
| 430 |
|
| 431 |
## 3. REST API
|
|
|
|
| 460 |
# With Thinking Dial
|
| 461 |
response = ollama.generate(
|
| 462 |
model="{model_name}",
|
| 463 |
+
prompt="<|think_depth_2|> Prove Pythagorean theorem",
|
| 464 |
)
|
| 465 |
|
| 466 |
print(response["response"])
|
models/thinking_dial.py
CHANGED
|
@@ -45,9 +45,9 @@ from transformers import PreTrainedModel, GenerationMixin
|
|
| 45 |
# 特殊 Token 定义
|
| 46 |
# ============================================================
|
| 47 |
|
| 48 |
-
THINK_START = "<|
|
| 49 |
THINK_END = "|>"
|
| 50 |
-
THINK_DEPTH_PATTERN = re.compile(r"<\|
|
| 51 |
|
| 52 |
# Depth 0-3 的描述
|
| 53 |
THINK_DEPTH_DESCRIPTIONS = {
|
|
@@ -71,7 +71,7 @@ def build_think_token(depth: int) -> str:
|
|
| 71 |
if not 0 <= depth <= 3:
|
| 72 |
raise ValueError(f"depth 必须在 0-3 之间,当前值:{depth}")
|
| 73 |
|
| 74 |
-
return f"{THINK_START}
|
| 75 |
|
| 76 |
|
| 77 |
def parse_think_token(text: str) -> Optional[int]:
|
|
|
|
| 45 |
# 特殊 Token 定义
|
| 46 |
# ============================================================
|
| 47 |
|
| 48 |
+
THINK_START = "<|think_depth_"
|
| 49 |
THINK_END = "|>"
|
| 50 |
+
THINK_DEPTH_PATTERN = re.compile(r"<\|think_depth_(\d+)\|>")
|
| 51 |
|
| 52 |
# Depth 0-3 的描述
|
| 53 |
THINK_DEPTH_DESCRIPTIONS = {
|
|
|
|
| 71 |
if not 0 <= depth <= 3:
|
| 72 |
raise ValueError(f"depth 必须在 0-3 之间,当前值:{depth}")
|
| 73 |
|
| 74 |
+
return f"{THINK_START}{depth}{THINK_END}"
|
| 75 |
|
| 76 |
|
| 77 |
def parse_think_token(text: str) -> Optional[int]:
|
scripts/fix_think_tokens.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Fix think token naming consistency across the project."""
|
| 3 |
+
import re
|
| 4 |
+
import glob
|
| 5 |
+
|
| 6 |
+
# Target format: <|think_depth_0|>, <|think_depth_1|>, etc.
|
| 7 |
+
|
| 8 |
+
def fix_file(filepath):
|
| 9 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 10 |
+
content = f.read()
|
| 11 |
+
|
| 12 |
+
original = content
|
| 13 |
+
|
| 14 |
+
# Replace THINK_START/THINK_END constants
|
| 15 |
+
content = content.replace('THINK_START = "<|think_depth_"', 'THINK_START = "<|think_depth_"')
|
| 16 |
+
content = content.replace('THINK_END = "|>"', 'THINK_END = "|>"')
|
| 17 |
+
|
| 18 |
+
# Replace build_think_token return
|
| 19 |
+
content = content.replace(
|
| 20 |
+
'return f"{THINK_START}{depth}{THINK_END}"',
|
| 21 |
+
'return f"{THINK_START}{depth}{THINK_END}"'
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
# Replace THINK_DEPTH_PATTERN regex
|
| 25 |
+
content = content.replace(
|
| 26 |
+
'THINK_DEPTH_PATTERN = re.compile(r"<\\|think\\| depth=(\\d+)\\|>")',
|
| 27 |
+
'THINK_DEPTH_PATTERN = re.compile(r"<\\|think_depth_(\\d+)\\|>")'
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Replace any inline <|think| depth=N|> with <|think_depth_N|>
|
| 31 |
+
content = re.sub(r'<\|think\|\s*depth=(\d+)\|>', r'<|think_depth_\1|>', content)
|
| 32 |
+
|
| 33 |
+
if content != original:
|
| 34 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 35 |
+
f.write(content)
|
| 36 |
+
print(f" Fixed: {filepath}")
|
| 37 |
+
return True
|
| 38 |
+
else:
|
| 39 |
+
print(f" No change: {filepath}")
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
files = glob.glob("**/*.py", recursive=True) + glob.glob("**/*.json", recursive=True)
|
| 43 |
+
fixed = 0
|
| 44 |
+
for f in sorted(files):
|
| 45 |
+
# Skip data files and output
|
| 46 |
+
if any(skip in f for skip in ['node_modules', '.git', 'output/']):
|
| 47 |
+
continue
|
| 48 |
+
try:
|
| 49 |
+
with open(f, 'r', encoding='utf-8') as fh:
|
| 50 |
+
text = fh.read()
|
| 51 |
+
if '<|think|' in text or 'think| depth=' in text:
|
| 52 |
+
if fix_file(f):
|
| 53 |
+
fixed += 1
|
| 54 |
+
except:
|
| 55 |
+
pass
|
| 56 |
+
|
| 57 |
+
print(f"\nTotal files fixed: {fixed}")
|
tests/run_tests.py
CHANGED
|
@@ -87,7 +87,7 @@ class TestThinkingDial(unittest.TestCase):
|
|
| 87 |
|
| 88 |
# 测试解析
|
| 89 |
depth, clean = processor.parse_thinking_depth(
|
| 90 |
-
"<|
|
| 91 |
)
|
| 92 |
|
| 93 |
self.assertEqual(depth, 2)
|
|
@@ -109,7 +109,7 @@ class TestThinkingDial(unittest.TestCase):
|
|
| 109 |
depth=1,
|
| 110 |
)
|
| 111 |
|
| 112 |
-
self.assertIn("<|
|
| 113 |
print("✅ Thinking Dial 注入测试通过")
|
| 114 |
|
| 115 |
|
|
|
|
| 87 |
|
| 88 |
# 测试解析
|
| 89 |
depth, clean = processor.parse_thinking_depth(
|
| 90 |
+
"<|think_depth_2|> 证明勾股定理"
|
| 91 |
)
|
| 92 |
|
| 93 |
self.assertEqual(depth, 2)
|
|
|
|
| 109 |
depth=1,
|
| 110 |
)
|
| 111 |
|
| 112 |
+
self.assertIn("<|think_depth_1|>", result)
|
| 113 |
print("✅ Thinking Dial 注入测试通过")
|
| 114 |
|
| 115 |
|
tokenizer.json
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"version": "1.0",
|
| 3 |
-
"truncation": null,
|
| 4 |
-
"padding": null,
|
| 5 |
-
"added_tokens": [
|
| 6 |
-
{"id": 0, "content": "<pad>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 7 |
-
{"id": 1, "content": "<s>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 8 |
-
{"id": 2, "content": "</s>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 9 |
-
{"id": 3, "content": "<unk>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 10 |
-
{"id": 32000, "content": "<|think| depth=0|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 11 |
-
{"id": 32001, "content": "<|think| depth=1|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 12 |
-
{"id": 32002, "content": "<|think| depth=2|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true},
|
| 13 |
-
{"id": 32003, "content": "<|think| depth=3|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": false, "special": true}
|
| 14 |
-
],
|
| 15 |
-
"normalizer": {
|
| 16 |
-
"type": "PrependIfMissing",
|
| 17 |
-
"prepend": "</s>",
|
| 18 |
-
"add_prefix_space": true
|
| 19 |
-
},
|
| 20 |
-
"pre_tokenizer": {
|
| 21 |
-
"type": "ByteLevel",
|
| 22 |
-
"add_prefix_space": true,
|
| 23 |
-
"trim_offsets": false
|
| 24 |
-
},
|
| 25 |
-
"post_processor": {
|
| 26 |
-
"type": "ByteLevel",
|
| 27 |
-
"add_prefix_space": true,
|
| 28 |
-
"trim_offsets": false
|
| 29 |
-
},
|
| 30 |
-
"decoder": {
|
| 31 |
-
"type": "ByteLevel",
|
| 32 |
-
"add_prefix_space": true,
|
| 33 |
-
"trim_offsets": false
|
| 34 |
-
},
|
| 35 |
-
"model": {
|
| 36 |
-
"type": "BPE",
|
| 37 |
-
"vocab": {},
|
| 38 |
-
"merges": []
|
| 39 |
-
}
|
| 40 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tokenizer_config.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"add_bos_token": true,
|
| 3 |
-
"add_eos_token": false,
|
| 4 |
-
"bos_token": "<s>",
|
| 5 |
-
"eos_token": "</s>",
|
| 6 |
-
"pad_token": "<pad>",
|
| 7 |
-
"unk_token": "<unk>",
|
| 8 |
-
"model_max_length": 32768,
|
| 9 |
-
"tokenizer_type": "SentencePiece",
|
| 10 |
-
"clean_up_tokenization_spaces": true
|
| 11 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
train/dpo_finetune.py
CHANGED
|
@@ -78,7 +78,7 @@ class DPOTrainer:
|
|
| 78 |
"""Get tokenizer with fallback to character-level encoding."""
|
| 79 |
try:
|
| 80 |
from models.tokenizer import get_tokenizer
|
| 81 |
-
return get_tokenizer("
|
| 82 |
except Exception:
|
| 83 |
return None
|
| 84 |
|
|
|
|
| 78 |
"""Get tokenizer with fallback to character-level encoding."""
|
| 79 |
try:
|
| 80 |
from models.tokenizer import get_tokenizer
|
| 81 |
+
return get_tokenizer("fusion")
|
| 82 |
except Exception:
|
| 83 |
return None
|
| 84 |
|
train/full_finetune.py
CHANGED
|
@@ -158,12 +158,9 @@ def create_local_model(
|
|
| 158 |
return model, config
|
| 159 |
|
| 160 |
|
| 161 |
-
def create_tokenizer(tokenizer_type: str = "
|
| 162 |
"""
|
| 163 |
Create tokenizer using the unified tokenizer module.
|
| 164 |
-
|
| 165 |
-
Note: Currently uses GPT2 as placeholder until SentencePiece model is trained.
|
| 166 |
-
The model config vocab_size will be auto-adjusted to match.
|
| 167 |
"""
|
| 168 |
effective_vocab = get_effective_vocab_size(tokenizer_type, vocab_size)
|
| 169 |
logger.info(f"[create_tokenizer] Creating tokenizer: type={tokenizer_type}, effective_vocab={effective_vocab}")
|
|
|
|
| 158 |
return model, config
|
| 159 |
|
| 160 |
|
| 161 |
+
def create_tokenizer(tokenizer_type: str = "fusion", vocab_size: int = 32000):
|
| 162 |
"""
|
| 163 |
Create tokenizer using the unified tokenizer module.
|
|
|
|
|
|
|
|
|
|
| 164 |
"""
|
| 165 |
effective_vocab = get_effective_vocab_size(tokenizer_type, vocab_size)
|
| 166 |
logger.info(f"[create_tokenizer] Creating tokenizer: type={tokenizer_type}, effective_vocab={effective_vocab}")
|
train/lora_finetune.py
CHANGED
|
@@ -196,27 +196,24 @@ def create_local_model(
|
|
| 196 |
|
| 197 |
def create_tokenizer(vocab_size: int = 32000):
|
| 198 |
"""
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
使用 GPT2Tokenizer 作为基础,resize 到匹配的 vocab 大小
|
| 202 |
"""
|
| 203 |
-
logger.info(f"[create_tokenizer]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
-
# 使用 GPT2 tokenizer 作为基础
|
| 206 |
tokenizer = AutoTokenizer.from_pretrained("gpt2")
|
| 207 |
tokenizer.pad_token = tokenizer.eos_token
|
| 208 |
-
|
| 209 |
-
# 如果 vocab_size 与 GPT2 不同,调整 embedding 层
|
| 210 |
-
if vocab_size != tokenizer.vocab_size:
|
| 211 |
-
logger.info(f"[create_tokenizer] 调整词表大小:{tokenizer.vocab_size} -> {vocab_size}")
|
| 212 |
-
model_torch_dtype = torch.bfloat16
|
| 213 |
-
# 获取模型的 embedding 层(在 create_local_model 中创建)
|
| 214 |
-
# 这里先 resize tokenizer,实际 embedding 在模型中也会自动处理
|
| 215 |
-
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
|
| 216 |
-
|
| 217 |
return tokenizer
|
| 218 |
|
| 219 |
|
|
|
|
| 220 |
def apply_lora(
|
| 221 |
model,
|
| 222 |
lora_rank: int = 64,
|
|
@@ -343,7 +340,7 @@ def main():
|
|
| 343 |
parser.add_argument("--model_size", type=str, default="1.5B",
|
| 344 |
choices=["0.5B", "1.5B", "8B", "14B"],
|
| 345 |
help="模型大小(0.5B/1.5B/8B/14B)")
|
| 346 |
-
parser.add_argument("--local_model", action="store_true", default=True
|
| 347 |
help="使用本地 FusionModel(默认,无需预训练权重)")
|
| 348 |
parser.add_argument("--quantize", action="store_true",
|
| 349 |
help="是否使用量化(QLoRA)")
|
|
|
|
| 196 |
|
| 197 |
def create_tokenizer(vocab_size: int = 32000):
|
| 198 |
"""
|
| 199 |
+
Create tokenizer matching model vocab_size.
|
| 200 |
+
Uses unified tokenizer module with SentencePiece if available, falls back to GPT2.
|
|
|
|
| 201 |
"""
|
| 202 |
+
logger.info(f"[create_tokenizer] Creating tokenizer (vocab_size={vocab_size})")
|
| 203 |
+
|
| 204 |
+
try:
|
| 205 |
+
from models.tokenizer import get_tokenizer
|
| 206 |
+
tokenizer = get_tokenizer("fusion", vocab_size=vocab_size)
|
| 207 |
+
return tokenizer
|
| 208 |
+
except Exception as e:
|
| 209 |
+
logger.warning(f"Fusion tokenizer failed ({e}), falling back to GPT2")
|
| 210 |
|
|
|
|
| 211 |
tokenizer = AutoTokenizer.from_pretrained("gpt2")
|
| 212 |
tokenizer.pad_token = tokenizer.eos_token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
return tokenizer
|
| 214 |
|
| 215 |
|
| 216 |
+
|
| 217 |
def apply_lora(
|
| 218 |
model,
|
| 219 |
lora_rank: int = 64,
|
|
|
|
| 340 |
parser.add_argument("--model_size", type=str, default="1.5B",
|
| 341 |
choices=["0.5B", "1.5B", "8B", "14B"],
|
| 342 |
help="模型大小(0.5B/1.5B/8B/14B)")
|
| 343 |
+
parser.add_argument("--local_model", action="store_true", default=True,
|
| 344 |
help="使用本地 FusionModel(默认,无需预训练权重)")
|
| 345 |
parser.add_argument("--quantize", action="store_true",
|
| 346 |
help="是否使用量化(QLoRA)")
|
train/train_mini.py
CHANGED
|
@@ -36,6 +36,18 @@ project_root = Path(__file__).parent.parent
|
|
| 36 |
sys.path.insert(0, str(project_root))
|
| 37 |
|
| 38 |
from models.fusion_mini import FusionMini, FusionMiniConfig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
|
| 41 |
class MiniDataset(Dataset):
|
|
@@ -164,9 +176,14 @@ def train_mini_model(
|
|
| 164 |
|
| 165 |
# 2. 加载数据集
|
| 166 |
print(f"\n[数据] 加载数据集...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
dataset = MiniDataset(
|
| 168 |
data_path=data_path,
|
| 169 |
-
tokenizer=
|
| 170 |
max_length=max_length,
|
| 171 |
)
|
| 172 |
|
|
@@ -179,7 +196,7 @@ def train_mini_model(
|
|
| 179 |
# 3. 创建模型配置
|
| 180 |
print(f"\n[模型] 创建模型...")
|
| 181 |
config = FusionMiniConfig(
|
| 182 |
-
vocab_size=
|
| 183 |
hidden_size=hidden_size,
|
| 184 |
num_hidden_layers=num_hidden_layers,
|
| 185 |
num_attention_heads=4,
|
|
@@ -187,8 +204,9 @@ def train_mini_model(
|
|
| 187 |
max_position_embeddings=max_length,
|
| 188 |
)
|
| 189 |
|
| 190 |
-
#
|
| 191 |
-
|
|
|
|
| 192 |
|
| 193 |
print(f" 词表大小:{config.vocab_size}")
|
| 194 |
print(f" 隐层大小:{config.hidden_size}")
|
|
|
|
| 36 |
sys.path.insert(0, str(project_root))
|
| 37 |
|
| 38 |
from models.fusion_mini import FusionMini, FusionMiniConfig
|
| 39 |
+
from models.tokenizer import get_tokenizer
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _try_get_tokenizer():
|
| 43 |
+
"""Try to load Fusion tokenizer, return None on failure."""
|
| 44 |
+
try:
|
| 45 |
+
tok = get_tokenizer("fusion")
|
| 46 |
+
if tok is not None:
|
| 47 |
+
print(f" [Tokenizer] Loaded Fusion tokenizer, vocab_size={len(tok)}")
|
| 48 |
+
return tok
|
| 49 |
+
except Exception:
|
| 50 |
+
return None
|
| 51 |
|
| 52 |
|
| 53 |
class MiniDataset(Dataset):
|
|
|
|
| 176 |
|
| 177 |
# 2. 加载数据集
|
| 178 |
print(f"\n[数据] 加载数据集...")
|
| 179 |
+
tok = _try_get_tokenizer()
|
| 180 |
+
if tok is not None:
|
| 181 |
+
vocab_size = len(tok)
|
| 182 |
+
else:
|
| 183 |
+
vocab_size = 1000
|
| 184 |
dataset = MiniDataset(
|
| 185 |
data_path=data_path,
|
| 186 |
+
tokenizer=tok, # Use fusion tokenizer if available, else char-level
|
| 187 |
max_length=max_length,
|
| 188 |
)
|
| 189 |
|
|
|
|
| 196 |
# 3. 创建模型配置
|
| 197 |
print(f"\n[模型] 创建模型...")
|
| 198 |
config = FusionMiniConfig(
|
| 199 |
+
vocab_size=vocab_size,
|
| 200 |
hidden_size=hidden_size,
|
| 201 |
num_hidden_layers=num_hidden_layers,
|
| 202 |
num_attention_heads=4,
|
|
|
|
| 204 |
max_position_embeddings=max_length,
|
| 205 |
)
|
| 206 |
|
| 207 |
+
# If char-level tokenizer, adjust vocab_size from data
|
| 208 |
+
if tok is None and hasattr(dataset, 'char_to_idx'):
|
| 209 |
+
config.vocab_size = len(dataset.char_to_idx) + 10
|
| 210 |
|
| 211 |
print(f" 词表大小:{config.vocab_size}")
|
| 212 |
print(f" 隐层大小:{config.hidden_size}")
|