zhan1206 commited on
Commit
4cfb0d8
·
1 Parent(s): bae07a4

fix: F1 mask double-conversion NaN, F2 think token old format, S3 vocab sync, S1/S2 TODO, N1-N5

Browse files
configs/ds_zero3.json CHANGED
@@ -15,7 +15,7 @@
15
  },
16
  "overlap_comm": true,
17
  "contiguous_gradients": true,
18
- "sub_group_size": 1e9,
19
  "reduce_bucket_size": "auto",
20
  "stage3_prefetch_bucket_size": "auto",
21
  "stage3_param_persistence_threshold": "auto",
 
15
  },
16
  "overlap_comm": true,
17
  "contiguous_gradients": true,
18
+ "sub_group_size": 1e6,
19
  "reduce_bucket_size": "auto",
20
  "stage3_prefetch_bucket_size": "auto",
21
  "stage3_param_persistence_threshold": "auto",
configs/fusion-mini-config.json CHANGED
@@ -26,7 +26,6 @@
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",
 
26
  "tie_word_embeddings": false,
27
  "enable_thinking_dial": true,
28
  "num_thinking_depths": 4,
 
29
 
30
  "torch_dtype": "float32",
31
  "transformers_version": "4.36.0",
models/fusion_mini.py CHANGED
@@ -357,11 +357,9 @@ class FusionMini(PreTrainedModel):
357
  # 1. Embeddings
358
  hidden_states = self.embeddings(input_ids)
359
 
360
- # 2. 处理 attention_mask
361
- if attention_mask is not None:
362
- # 转换为 (batch, 1, 1, seq_len) 格式
363
- attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
364
- attention_mask = (1.0 - attention_mask) * -10000.0
365
 
366
  # 3. Transformer 层
367
  for layer in self.layers:
 
357
  # 1. Embeddings
358
  hidden_states = self.embeddings(input_ids)
359
 
360
+ # 2. 处理 attention_mask - pass raw HF format to layers
361
+ # Each FusionMiniLayer handles mask conversion internally
362
+ # DO NOT pre-convert here to avoid double-conversion NaN (F1)
 
 
363
 
364
  # 3. Transformer 层
365
  for layer in self.layers:
models/fusion_model.py CHANGED
@@ -96,8 +96,8 @@ class FusionConfig(PretrainedConfig):
96
  # SBLA 参数
97
  self.block_size = block_size
98
  self.latent_dim = latent_dim
99
- self.sbla_window_size = sbla_window_size or block_size
100
- self.window_size = window_size or self.sbla_window_size # Unified field
101
  self.sbla_mode = sbla_mode
102
 
103
  # Thinking Dial 参数
@@ -134,6 +134,10 @@ class FusionAttention(nn.Module):
134
  def __init__(self, config: FusionConfig):
135
  super().__init__()
136
 
 
 
 
 
137
  mode = getattr(config, 'sbla_mode', 'hybrid')
138
  self.sbla = SBLAttention(
139
  hidden_size=config.hidden_size,
@@ -152,12 +156,15 @@ class FusionAttention(nn.Module):
152
  past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
153
  use_cache: bool = False,
154
  ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
155
- # Delegate to SBLAttention
 
 
 
156
  output = self.sbla(hidden_states, attention_mask)
157
 
158
- # KV Cache: extract from SBLA's Q/K projections for cache reuse
159
  present_key_value = None
160
  if use_cache:
 
161
  batch_size, seq_len, _ = hidden_states.shape
162
  K = self.sbla.k_proj(hidden_states)
163
  V = self.sbla.v_proj(hidden_states)
@@ -277,12 +284,14 @@ class FusionModel(PreTrainedModel, GenerationMixin):
277
  else:
278
  raise ValueError("Either input_ids or inputs_embeds must be provided")
279
 
280
- # 处理 attention_mask
281
- if attention_mask is not None:
282
- if attention_mask.dim() == 2:
283
- attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
284
- float_mask = attention_mask.to(dtype=hidden_states.dtype)
285
- attention_mask = (1.0 - float_mask) * torch.finfo(hidden_states.dtype).min
 
 
286
 
287
  # Transformer 层(支持 KV Cache)
288
  past_key_values = kwargs.get("past_key_values", None)
 
96
  # SBLA 参数
97
  self.block_size = block_size
98
  self.latent_dim = latent_dim
99
+ self.window_size = window_size or sbla_window_size or block_size
100
+ self.sbla_window_size = self.window_size # Keep as alias for backward compat
101
  self.sbla_mode = sbla_mode
102
 
103
  # Thinking Dial 参数
 
134
  def __init__(self, config: FusionConfig):
135
  super().__init__()
136
 
137
+ # TODO(S2): GQA not yet implemented - num_key_value_heads is stored in config
138
+ # but SBLAttention uses num_attention_heads for all Q/K/V projections.
139
+ # To implement GQA: split Q/K/V projections, use num_key_value_heads for K/V,
140
+ # and add repeat_kv() expansion for attention computation.
141
  mode = getattr(config, 'sbla_mode', 'hybrid')
142
  self.sbla = SBLAttention(
143
  hidden_size=config.hidden_size,
 
156
  past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
157
  use_cache: bool = False,
158
  ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
159
+ # TODO(S1): KV Cache is currently decorative - SBLA recomputes full Q/K/V internally.
160
+ # For true incremental generation, SBLA.forward needs to accept past_key_value
161
+ # and only compute Q for new tokens, reusing cached K/V.
162
+ # Present implementation: always full recomputation, cache returned but unused.
163
  output = self.sbla(hidden_states, attention_mask)
164
 
 
165
  present_key_value = None
166
  if use_cache:
167
+ # Cache K/V for potential future use when SBLA supports incremental mode
168
  batch_size, seq_len, _ = hidden_states.shape
169
  K = self.sbla.k_proj(hidden_states)
170
  V = self.sbla.v_proj(hidden_states)
 
284
  else:
285
  raise ValueError("Either input_ids or inputs_embeds must be provided")
286
 
287
+ # 处理 attention_mask - pass raw HF format (1=valid, 0=padding) to SBLA
288
+ # SBLAttention handles the conversion internally
289
+ # DO NOT convert here - it would cause double-conversion NaN (F1)
290
+ # if attention_mask is not None:
291
+ # if attention_mask.dim() == 2:
292
+ # attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
293
+ # float_mask = attention_mask.to(dtype=hidden_states.dtype)
294
+ # attention_mask = (1.0 - float_mask) * torch.finfo(hidden_states.dtype).min
295
 
296
  # Transformer 层(支持 KV Cache)
297
  past_key_values = kwargs.get("past_key_values", None)
models/sbla_attention.py CHANGED
@@ -275,12 +275,24 @@ class SBLAttention(nn.Module):
275
  combined_mask = causal_mask
276
 
277
  # 应用外部 attention_mask(padding mask)
 
 
278
  if attention_mask is not None:
279
- # attention_mask: (batch, 1, 1, seq_len) -> 扩展为 (batch, 1, seq_len, seq_len)
280
- ext_mask = attention_mask.squeeze(1) # (batch, 1, seq_len)
281
- # padding 位置设为 -inf
282
- padding_mask = (1.0 - ext_mask) * float('-inf') # (batch, 1, seq_len)
283
- combined_mask = combined_mask.unsqueeze(0) + padding_mask.unsqueeze(1) # (batch, 1, seq_len, seq_len)
 
 
 
 
 
 
 
 
 
 
284
  else:
285
  combined_mask = combined_mask.unsqueeze(0) # (1, 1, seq_len, seq_len)
286
 
 
275
  combined_mask = causal_mask
276
 
277
  # 应用外部 attention_mask(padding mask)
278
+ # Supports both raw HF format (batch, seq_len) with 1=valid/0=padding,
279
+ # and pre-expanded format (batch, 1, 1, seq_len).
280
  if attention_mask is not None:
281
+ if attention_mask.dim() == 2:
282
+ # Raw HF format: (batch, seq_len), 1=valid, 0=padding
283
+ padding_mask = (1.0 - attention_mask.float()).unsqueeze(1).unsqueeze(2) # (batch, 1, 1, seq_len)
284
+ padding_mask = padding_mask * torch.finfo(hidden_states.dtype).min
285
+ combined_mask = combined_mask.unsqueeze(0).unsqueeze(0) + padding_mask # (1, 1, seq, seq) + (batch, 1, 1, seq)
286
+ elif attention_mask.dim() == 4:
287
+ # Pre-expanded format (already converted, e.g. from FusionMini)
288
+ ext_mask = attention_mask.squeeze(1) # (batch, 1, seq_len)
289
+ padding_mask = (1.0 - ext_mask) * float('-inf') # (batch, 1, seq_len)
290
+ combined_mask = combined_mask.unsqueeze(0) + padding_mask.unsqueeze(1)
291
+ else:
292
+ # 3D fallback
293
+ padding_mask = (1.0 - attention_mask.float()).unsqueeze(1) # (batch, 1, 1, seq_len)
294
+ padding_mask = padding_mask * torch.finfo(hidden_states.dtype).min
295
+ combined_mask = combined_mask.unsqueeze(0).unsqueeze(0) + padding_mask
296
  else:
297
  combined_mask = combined_mask.unsqueeze(0) # (1, 1, seq_len, seq_len)
298
 
models/thinking_dial.py CHANGED
@@ -36,7 +36,7 @@ Thinking Dial(动态推理强度控制)- 真实实现
36
  import torch
37
  import torch.nn as nn
38
  import re
39
- from typing import List, Dict, Optional, Any
40
  from dataclasses import dataclass
41
  from transformers import PreTrainedModel, GenerationMixin
42
 
 
36
  import torch
37
  import torch.nn as nn
38
  import re
39
+ from typing import List, Dict, Optional, Any, Tuple
40
  from dataclasses import dataclass
41
  from transformers import PreTrainedModel, GenerationMixin
42
 
scripts/fix_think_tokens.py CHANGED
@@ -1,57 +1,65 @@
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}")
 
 
 
 
 
 
 
 
1
  #!/usr/bin/env python3
2
+ """Fix think token format across the project.
3
+
4
+ Ensures all files use the unified <|think_depth_N|> format,
5
+ not the old <|think| depth=N|> format.
6
+ """
7
  import re
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # Old format patterns (to replace)
12
+ OLD_INLINE = re.compile(r'<\|think\|\s*depth=(\d+)\|>')
13
+ OLD_FSTRING = re.compile(r'f"<\|think\|\s*depth=\{(\w+)\}\|>"')
14
+ OLD_TEMPLATE = '<|think| depth='
15
 
16
+ # New format
17
+ NEW_INLINE = r'<|think_depth_\1|>'
18
+ NEW_FSTRING = r'f"<|think_depth_{\1}|>"'
19
 
20
+
21
+ def fix_file(filepath: Path) -> bool:
22
+ """Fix think tokens in a single file. Returns True if changes were made."""
23
+ try:
24
+ content = filepath.read_text(encoding='utf-8')
25
+ except Exception:
26
+ return False
27
 
28
  original = content
29
 
30
+ # Fix inline occurrences: <|think| depth=N|> -> <|think_depth_N|>
31
+ content = OLD_INLINE.sub(NEW_INLINE, content)
 
 
 
 
 
 
 
32
 
33
+ # Fix f-string templates: f"<|think| depth={var}|>" -> f"<|think_depth_{var}|>"
34
+ content = OLD_FSTRING.sub(NEW_FSTRING, content)
 
 
 
 
 
 
35
 
36
  if content != original:
37
+ filepath.write_text(content, encoding='utf-8')
38
+ count = sum(1 for a, b in zip(original.split('\n'), content.split('\n')) if a != b)
39
+ print(f" Fixed {filepath} ({count} lines)")
40
  return True
41
+ return False
 
 
42
 
43
+
44
+ def main():
45
+ project_root = Path(__file__).parent.parent
46
+
47
+ # Scan all Python files
48
+ changed = 0
49
+ for py_file in project_root.rglob('*.py'):
50
+ if fix_file(py_file):
51
+ changed += 1
52
+
53
+ # Also check JSON data files
54
+ for json_file in project_root.rglob('*.json'):
55
+ if 'node_modules' in str(json_file):
56
+ continue
57
+ if fix_file(json_file):
58
+ changed += 1
59
+
60
+ print(f"\nTotal files fixed: {changed}")
61
+ return 0
62
+
63
+
64
+ if __name__ == '__main__':
65
+ sys.exit(main())
train/full_finetune.py CHANGED
@@ -89,7 +89,7 @@ class FusionFullFinetuneDataset(Dataset):
89
  think_rank = item.get("think_rank", 0)
90
 
91
  if think_rank > 0:
92
- thinking_token = f"<|think| depth={think_rank}|>"
93
  full_text = f"{thinking_token}\n{prompt}\n{response}"
94
  else:
95
  full_text = f"{prompt}\n{response}"
@@ -112,6 +112,7 @@ class FusionFullFinetuneDataset(Dataset):
112
  def create_local_model(
113
  model_size: str = "8B",
114
  torch_dtype: torch.dtype = torch.bfloat16,
 
115
  ):
116
  """
117
  创建本地 FusionModel(无需预训练权重)
@@ -132,6 +133,12 @@ def create_local_model(
132
 
133
  config_dict = model_configs[model_size]
134
 
 
 
 
 
 
 
135
  common_config = dict(
136
  block_size=512,
137
  latent_dim=64,
@@ -146,6 +153,18 @@ def create_local_model(
146
 
147
  config = FusionConfig(**config_dict, **common_config)
148
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  logger.info(f"[create_local_model] 创建 Fusion-{model_size}(随机初始化)")
150
  logger.info(f" hidden_size={config.hidden_size}, layers={config.num_hidden_layers}, "
151
  f"heads={config.num_attention_heads}")
@@ -162,8 +181,7 @@ 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}")
167
  tokenizer = get_tokenizer(tokenizer_type, vocab_size=vocab_size)
168
  return tokenizer
169
 
@@ -192,8 +210,14 @@ def train(args):
192
  vocab_size_map = {"0.5B": 32000, "1.5B": 32000, "8B": 100000, "14B": 100000}
193
  tokenizer = create_tokenizer(vocab_size=vocab_size_map.get(args.model_size, 32000))
194
 
 
 
 
 
 
 
195
  # 3. 创建模型(本地随机初始化)
196
- model, config = create_local_model(args.model_size, torch_dtype=args.torch_dtype)
197
 
198
  # 4. 加载数据集
199
  train_dataset = FusionFullFinetuneDataset(
 
89
  think_rank = item.get("think_rank", 0)
90
 
91
  if think_rank > 0:
92
+ thinking_token = f"<|think_depth_{think_rank}|>"
93
  full_text = f"{thinking_token}\n{prompt}\n{response}"
94
  else:
95
  full_text = f"{prompt}\n{response}"
 
112
  def create_local_model(
113
  model_size: str = "8B",
114
  torch_dtype: torch.dtype = torch.bfloat16,
115
+ vocab_size_override: Optional[int] = None,
116
  ):
117
  """
118
  创建本地 FusionModel(无需预训练权重)
 
133
 
134
  config_dict = model_configs[model_size]
135
 
136
+ # S3 fix: override vocab_size to match actual tokenizer
137
+ if vocab_size_override is not None:
138
+ config_dict['vocab_size'] = vocab_size_override
139
+ if vocab_size_override is not None:
140
+ config_dict['vocab_size'] = vocab_size_override
141
+
142
  common_config = dict(
143
  block_size=512,
144
  latent_dim=64,
 
153
 
154
  config = FusionConfig(**config_dict, **common_config)
155
 
156
+ # Override vocab_size if tokenizer has different size (S3 fix)
157
+ if vocab_size_override is not None and vocab_size_override != config.vocab_size:
158
+ logger.warning(f"[S3-fix] Overriding model vocab_size: {config.vocab_size} -> {vocab_size_override}")
159
+ config.vocab_size = vocab_size_override
160
+
161
+ # S3-fix: sync vocab_size to actual tokenizer if provided
162
+ if vocab_size_override is not None:
163
+ if vocab_size_override != config.vocab_size:
164
+ logger.warning(f"[S3-fix] Overriding vocab_size: {config.vocab_size} -> {vocab_size_override}")
165
+ model.resize_token_embeddings(vocab_size_override)
166
+ config.vocab_size = vocab_size_override
167
+
168
  logger.info(f"[create_local_model] 创建 Fusion-{model_size}(随机初始化)")
169
  logger.info(f" hidden_size={config.hidden_size}, layers={config.num_hidden_layers}, "
170
  f"heads={config.num_attention_heads}")
 
181
  """
182
  Create tokenizer using the unified tokenizer module.
183
  """
184
+ logger.info(f"[create_tokenizer] Creating tokenizer: type={tokenizer_type}, vocab_size={vocab_size}")
 
185
  tokenizer = get_tokenizer(tokenizer_type, vocab_size=vocab_size)
186
  return tokenizer
187
 
 
210
  vocab_size_map = {"0.5B": 32000, "1.5B": 32000, "8B": 100000, "14B": 100000}
211
  tokenizer = create_tokenizer(vocab_size=vocab_size_map.get(args.model_size, 32000))
212
 
213
+ # Sync vocab_size to actual tokenizer size to prevent index-out-of-range (S3)
214
+ actual_vocab_size = len(tokenizer)
215
+ if actual_vocab_size != vocab_size_map.get(args.model_size, 32000):
216
+ logger.warning(f"[S3-fix] Vocab size mismatch: config={vocab_size_map.get(args.model_size, 32000)}, tokenizer={actual_vocab_size}. Syncing to tokenizer.")
217
+ vocab_size_map[args.model_size] = actual_vocab_size
218
+
219
  # 3. 创建模型(本地随机初始化)
220
+ model, config = create_local_model(args.model_size, torch_dtype=args.torch_dtype, vocab_size_override=actual_vocab_size)
221
 
222
  # 4. 加载数据集
223
  train_dataset = FusionFullFinetuneDataset(
train/lora_finetune.py CHANGED
@@ -104,7 +104,7 @@ class FusionDataset(Dataset):
104
 
105
  # 注入 Thinking Dial 控制 token
106
  if self.add_thinking_token and think_rank > 0:
107
- thinking_token = f"<|think| depth={think_rank}|>"
108
  full_text = f"{thinking_token}\n{prompt}\n{response}"
109
  else:
110
  full_text = f"{prompt}\n{response}"
 
104
 
105
  # 注入 Thinking Dial 控制 token
106
  if self.add_thinking_token and think_rank > 0:
107
+ thinking_token = f"<|think_depth_{think_rank}|>"
108
  full_text = f"{thinking_token}\n{prompt}\n{response}"
109
  else:
110
  full_text = f"{prompt}\n{response}"