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 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, num_layers=2)
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("gpt2"))
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("gpt2")
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
- # <|think| depth=0|> 简单问题,直接回答
185
- # <|think| depth=3|> 复杂问题,详细推理
186
  """
187
 
188
  # 写入文件
@@ -319,16 +319,16 @@ Fusion 支持动态推理强度控制。在问题前添加控制 token:
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,7 +363,7 @@ print(response["response"])
363
  # 带 Thinking Dial
364
  response = ollama.generate(
365
  model="{model_name}",
366
- prompt="<|think| depth=2|> 证明勾股定理",
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
- # <|think| depth=0|> Simple question, direct answer
270
- # <|think| depth=3|> Complex question, detailed reasoning
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
- > <|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,7 +460,7 @@ print(response["response"])
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"])
 
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 = "<|think|"
49
  THINK_END = "|>"
50
- THINK_DEPTH_PATTERN = re.compile(r"<\|think\| depth=(\d+)\|>")
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} depth={depth}{THINK_END}"
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
- "<|think| depth=2|> 证明勾股定理"
91
  )
92
 
93
  self.assertEqual(depth, 2)
@@ -109,7 +109,7 @@ class TestThinkingDial(unittest.TestCase):
109
  depth=1,
110
  )
111
 
112
- self.assertIn("<|think| depth=1|>", result)
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("gpt2")
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 = "gpt2", vocab_size: int = 32000):
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
- 创建与模型 vocab_size 匹配的 tokenizer
200
-
201
- 使用 GPT2Tokenizer 作为基础,resize 到匹配的 vocab 大小
202
  """
203
- logger.info(f"[create_tokenizer] 创建 tokenizervocab_size={vocab_size}")
 
 
 
 
 
 
 
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=None, # 使用字符级编码
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=1000, # 字符级,实际会根据数据调整
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
- config.vocab_size = len(dataset.char_to_idx) + 10 # 加点余量
 
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}")