zhan1206 commited on
Commit
a959e9a
·
1 Parent(s): d5ef945

fix: comprehensive audit fixes - CRITICAL/HIGH/MEDIUM/LOW

Browse files

CRITICAL (5):
- C1: Fix import torch.nn as F -> torch.nn.functional as F
- C2/C3: Return CausalLMOutputWithPast instead of plain dict
- C4: Proper BitsAndBytesConfig for QLoRA with graceful fallback
- C5: Unify return types, update callers to .logits/.past_key_values

HIGH (10):
- H3: AutoConfig registration for fusion and fusion_mini
- H4-H6: try/except relative->absolute import fallback pattern
- H7: Window mask clamping to prevent degenerate masks
- H8-H9: Optional deps (DeepSpeed, bitsandbytes) try/except
- H10: DPO loss manual impl confirmed correct, import paths fixed

MEDIUM:
- M1-M3: Think token registration with additional_special_tokens
- M4-M5: Fix double label shift in train_real.py
- M14: Add Rotary Position Embedding (RoPE) to FusionModel
- Perplexity: Fix dict vs attribute access for loss
- 4-bit quant: Clarify comment (real quantization, not fake)

LOW:
- Add deterministic seed (42) to train_mini.py
- Mark GGML/ONNX exports as stub/simplified

RELEASE_v1.2.0.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fusion-LLM v1.2.0 Release Notes
2
+
3
+ ## New Features
4
+
5
+ ### Evaluation Metrics
6
+ - **BLEU/ROUGE/METEOR** (`evaluation/bleu_rouge_meteor.py`) - Standard NLP evaluation suite with precision, recall, F1
7
+
8
+ ### Deployment Options
9
+ - **TensorRT** (`deployment/export_tensorrt_openvino.py`) - GPU-optimized inference via ONNX to TensorRT engine
10
+ - **OpenVINO** (`deployment/export_tensorrt_openvino.py`) - CPU-optimized inference via ONNX to OpenVINO IR
11
+
12
+ ### Training Optimization
13
+ - **AMP Trainer** (`train/training_optimizations.py`) - Automatic Mixed Precision training with gradient scaling
14
+ - **Gradient Accumulation** (`train/training_optimizations.py`) - Simulate larger batch sizes on limited VRAM
15
+
16
+ ### Model Interpretability
17
+ - **LIME** (`evaluation/model_interpretability.py`) - Token-level importance via perturbation
18
+ - **SHAP** (`evaluation/model_interpretability.py`) - Shapley value-based token contribution analysis
19
+
20
+ ## How to Create This Release on GitHub
21
+
22
+ 1. Go to https://github.com/zhan1206/fusion-llm/releases/new
23
+ 2. Tag: **v1.2.0**
24
+ 3. Title: **Fusion-LLM v1.2.0**
25
+ 4. Copy the content above as the description
26
+ 5. Click **Publish release**
data/training_data.txt ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The cat sits on the mat.
2
+ A dog runs in the park.
3
+ Birds fly in the sky.
4
+ Fish swim in the sea.
5
+ Children play in the garden.
6
+ The sun is shining brightly.
7
+ It is raining heavily today.
8
+ Snow falls in winter.
9
+ Flowers bloom in spring.
10
+ Leaves fall in autumn.
11
+ I love reading books.
12
+ She writes a letter.
13
+ He cooks dinner for us.
14
+ We watch a movie together.
15
+ They sing a beautiful song.
16
+ The car moves fast on the road.
17
+ A plane flies in the air.
18
+ Ships sail on the ocean.
19
+ Trains travel across the country.
20
+ Bicycles are good for health.
21
+ Apple is a delicious fruit.
22
+ Water is essential for life.
23
+ The house has a big garden.
24
+ Music brings joy to people.
25
+ Learning is a lifelong journey.
26
+ The cat sits on the mat.
27
+ A dog runs in the park.
28
+ Birds fly in the sky.
29
+ Fish swim in the sea.
30
+ Children play in the garden.
31
+ The sun is shining brightly.
32
+ It is raining heavily today.
33
+ Snow falls in winter.
34
+ Flowers bloom in spring.
35
+ Leaves fall in autumn.
36
+ I love reading books.
37
+ She writes a letter.
38
+ He cooks dinner for us.
39
+ We watch a movie together.
40
+ They sing a beautiful song.
41
+ The car moves fast on the road.
42
+ A plane flies in the air.
43
+ Ships sail on the ocean.
44
+ Trains travel across the country.
45
+ Bicycles are good for health.
46
+ Apple is a delicious fruit.
47
+ Water is essential for life.
48
+ The house has a big garden.
49
+ Music brings joy to people.
50
+ Learning is a lifelong journey.
51
+ The cat sits on the mat.
52
+ A dog runs in the park.
53
+ Birds fly in the sky.
54
+ Fish swim in the sea.
55
+ Children play in the garden.
56
+ The sun is shining brightly.
57
+ It is raining heavily today.
58
+ Snow falls in winter.
59
+ Flowers bloom in spring.
60
+ Leaves fall in autumn.
61
+ I love reading books.
62
+ She writes a letter.
63
+ He cooks dinner for us.
64
+ We watch a movie together.
65
+ They sing a beautiful song.
66
+ The car moves fast on the road.
67
+ A plane flies in the air.
68
+ Ships sail on the ocean.
69
+ Trains travel across the country.
70
+ Bicycles are good for health.
71
+ Apple is a delicious fruit.
72
+ Water is essential for life.
73
+ The house has a big garden.
74
+ Music brings joy to people.
75
+ Learning is a lifelong journey.
76
+ The cat sits on the mat.
77
+ A dog runs in the park.
78
+ Birds fly in the sky.
79
+ Fish swim in the sea.
80
+ Children play in the garden.
81
+ The sun is shining brightly.
82
+ It is raining heavily today.
83
+ Snow falls in winter.
84
+ Flowers bloom in spring.
85
+ Leaves fall in autumn.
86
+ I love reading books.
87
+ She writes a letter.
88
+ He cooks dinner for us.
89
+ We watch a movie together.
90
+ They sing a beautiful song.
91
+ The car moves fast on the road.
92
+ A plane flies in the air.
93
+ Ships sail on the ocean.
94
+ Trains travel across the country.
95
+ Bicycles are good for health.
96
+ Apple is a delicious fruit.
97
+ Water is essential for life.
98
+ The house has a big garden.
99
+ Music brings joy to people.
100
+ Learning is a lifelong journey.
101
+ The cat sits on the mat.
102
+ A dog runs in the park.
103
+ Birds fly in the sky.
104
+ Fish swim in the sea.
105
+ Children play in the garden.
106
+ The sun is shining brightly.
107
+ It is raining heavily today.
108
+ Snow falls in winter.
109
+ Flowers bloom in spring.
110
+ Leaves fall in autumn.
111
+ I love reading books.
112
+ She writes a letter.
113
+ He cooks dinner for us.
114
+ We watch a movie together.
115
+ They sing a beautiful song.
116
+ The car moves fast on the road.
117
+ A plane flies in the air.
118
+ Ships sail on the ocean.
119
+ Trains travel across the country.
120
+ Bicycles are good for health.
121
+ Apple is a delicious fruit.
122
+ Water is essential for life.
123
+ The house has a big garden.
124
+ Music brings joy to people.
125
+ Learning is a lifelong journey.
126
+ The cat sits on the mat.
127
+ A dog runs in the park.
128
+ Birds fly in the sky.
129
+ Fish swim in the sea.
130
+ Children play in the garden.
131
+ The sun is shining brightly.
132
+ It is raining heavily today.
133
+ Snow falls in winter.
134
+ Flowers bloom in spring.
135
+ Leaves fall in autumn.
136
+ I love reading books.
137
+ She writes a letter.
138
+ He cooks dinner for us.
139
+ We watch a movie together.
140
+ They sing a beautiful song.
141
+ The car moves fast on the road.
142
+ A plane flies in the air.
143
+ Ships sail on the ocean.
144
+ Trains travel across the country.
145
+ Bicycles are good for health.
146
+ Apple is a delicious fruit.
147
+ Water is essential for life.
148
+ The house has a big garden.
149
+ Music brings joy to people.
150
+ Learning is a lifelong journey.
151
+ The cat sits on the mat.
152
+ A dog runs in the park.
153
+ Birds fly in the sky.
154
+ Fish swim in the sea.
155
+ Children play in the garden.
156
+ The sun is shining brightly.
157
+ It is raining heavily today.
158
+ Snow falls in winter.
159
+ Flowers bloom in spring.
160
+ Leaves fall in autumn.
161
+ I love reading books.
162
+ She writes a letter.
163
+ He cooks dinner for us.
164
+ We watch a movie together.
165
+ They sing a beautiful song.
166
+ The car moves fast on the road.
167
+ A plane flies in the air.
168
+ Ships sail on the ocean.
169
+ Trains travel across the country.
170
+ Bicycles are good for health.
171
+ Apple is a delicious fruit.
172
+ Water is essential for life.
173
+ The house has a big garden.
174
+ Music brings joy to people.
175
+ Learning is a lifelong journey.
176
+ The cat sits on the mat.
177
+ A dog runs in the park.
178
+ Birds fly in the sky.
179
+ Fish swim in the sea.
180
+ Children play in the garden.
181
+ The sun is shining brightly.
182
+ It is raining heavily today.
183
+ Snow falls in winter.
184
+ Flowers bloom in spring.
185
+ Leaves fall in autumn.
186
+ I love reading books.
187
+ She writes a letter.
188
+ He cooks dinner for us.
189
+ We watch a movie together.
190
+ They sing a beautiful song.
191
+ The car moves fast on the road.
192
+ A plane flies in the air.
193
+ Ships sail on the ocean.
194
+ Trains travel across the country.
195
+ Bicycles are good for health.
196
+ Apple is a delicious fruit.
197
+ Water is essential for life.
198
+ The house has a big garden.
199
+ Music brings joy to people.
200
+ Learning is a lifelong journey.
201
+ The cat sits on the mat.
202
+ A dog runs in the park.
203
+ Birds fly in the sky.
204
+ Fish swim in the sea.
205
+ Children play in the garden.
206
+ The sun is shining brightly.
207
+ It is raining heavily today.
208
+ Snow falls in winter.
209
+ Flowers bloom in spring.
210
+ Leaves fall in autumn.
211
+ I love reading books.
212
+ She writes a letter.
213
+ He cooks dinner for us.
214
+ We watch a movie together.
215
+ They sing a beautiful song.
216
+ The car moves fast on the road.
217
+ A plane flies in the air.
218
+ Ships sail on the ocean.
219
+ Trains travel across the country.
220
+ Bicycles are good for health.
221
+ Apple is a delicious fruit.
222
+ Water is essential for life.
223
+ The house has a big garden.
224
+ Music brings joy to people.
225
+ Learning is a lifelong journey.
226
+ The cat sits on the mat.
227
+ A dog runs in the park.
228
+ Birds fly in the sky.
229
+ Fish swim in the sea.
230
+ Children play in the garden.
231
+ The sun is shining brightly.
232
+ It is raining heavily today.
233
+ Snow falls in winter.
234
+ Flowers bloom in spring.
235
+ Leaves fall in autumn.
236
+ I love reading books.
237
+ She writes a letter.
238
+ He cooks dinner for us.
239
+ We watch a movie together.
240
+ They sing a beautiful song.
241
+ The car moves fast on the road.
242
+ A plane flies in the air.
243
+ Ships sail on the ocean.
244
+ Trains travel across the country.
245
+ Bicycles are good for health.
246
+ Apple is a delicious fruit.
247
+ Water is essential for life.
248
+ The house has a big garden.
249
+ Music brings joy to people.
250
+ Learning is a lifelong journey.
deployment/export_ggml.py CHANGED
@@ -24,7 +24,9 @@ def export_to_ggml(model, tokenizer, output_path, vocab_size=32000):
24
  output_path: 输出路径(.ggml 文件)
25
  vocab_size: 词汇表大小
26
  """
27
- print("[EXPORT] 导出模型到 GGML 格式(简化版)...")
 
 
28
 
29
  # 获取模型配置
30
  if hasattr(model.config, 'vocab_size'):
 
24
  output_path: 输出路径(.ggml 文件)
25
  vocab_size: 词汇表大小
26
  """
27
+ # TODO: This is a simplified/stub export. For production use,
28
+ # use llama.cpp's convert.py and quantize tools instead.
29
+ print("[EXPORT] 导出模型到 GGML 格式(简化版 - 仅保存配置,非可用 GGML 权重)...")
30
 
31
  # 获取模型配置
32
  if hasattr(model.config, 'vocab_size'):
deployment/export_onnx.py CHANGED
@@ -23,7 +23,9 @@ def export_to_onnx(model, tokenizer, output_path, dummy_input=None):
23
  output_path: 输出路径(.onnx 文件)
24
  dummy_input: 虚拟输入(用于导出)
25
  """
26
- print("[EXPORT] 导出模型到 ONNX 格式(简化版)...")
 
 
27
 
28
  # 创建虚拟输入(如果没有提供)
29
  if dummy_input is None:
 
23
  output_path: 输出路径(.onnx 文件)
24
  dummy_input: 虚拟输入(用于导出)
25
  """
26
+ # TODO: This is a simplified/stub export. For production use,
27
+ # use torch.onnx.export() directly with proper opset version.
28
+ print("[EXPORT] 导出模型到 ONNX 格式(简化版 - 仅保存配置和元数据)...")
29
 
30
  # 创建虚拟输入(如果没有提供)
31
  if dummy_input is None:
evaluation/metrics.py CHANGED
@@ -94,7 +94,8 @@ class ModelEvaluator:
94
  )
95
 
96
  # 计算困惑度
97
- loss = outputs["loss"] if isinstance(outputs, dict) else outputs.loss
 
98
  perplexity = torch.exp(loss).item()
99
 
100
  return perplexity
 
94
  )
95
 
96
  # 计算困惑度
97
+ loss = outputs.loss if hasattr(outputs, 'loss') else outputs["loss"]
98
+ # loss is already mean-reduced over tokens by model, so PPL = exp(loss)
99
  perplexity = torch.exp(loss).item()
100
 
101
  return perplexity
inference/dyquant.py CHANGED
@@ -283,8 +283,8 @@ class DyQuantConverter:
283
  )
284
  return quantized
285
  elif bits == 4:
286
- # 4-bit 量化需要更复杂的实现
287
- # 这里使int8 模拟 + 缩放近似
288
  return self._quantize_to_nbit(layer, 4)
289
  else:
290
  # 不量化
 
283
  )
284
  return quantized
285
  elif bits == 4:
286
+ # 4-bit 对称量化(per-channel scale + zero_point)
287
+ # 注意:此为训练感知近似量化,非推理加速专INT4 kernel
288
  return self._quantize_to_nbit(layer, 4)
289
  else:
290
  # 不量化
models/fusion_mini.py CHANGED
@@ -39,14 +39,19 @@ import torch
39
  import torch.nn as nn
40
  import torch.nn.functional as F
41
  from transformers import PretrainedConfig, PreTrainedModel
 
42
  from typing import Optional, Tuple
43
  import math
44
  import json
45
  from pathlib import Path
46
 
47
- # 导入 SBLA 注意力
48
- from .sbla_attention import SBLAttention
49
- from .fusion_model import RMSNorm
 
 
 
 
50
 
51
 
52
  class FusionMiniConfig(PretrainedConfig):
@@ -106,6 +111,14 @@ class FusionMiniConfig(PretrainedConfig):
106
  raise FileNotFoundError(f"配置文件未找到:{config_file}")
107
 
108
 
 
 
 
 
 
 
 
 
109
  class FusionMiniEmbeddings(nn.Module):
110
  """
111
  Fusion Mini 词嵌入
@@ -348,16 +361,16 @@ class FusionMini(PreTrainedModel):
348
  past_key_values: Optional[Tuple] = None,
349
  use_cache: Optional[bool] = None,
350
  return_dict: Optional[bool] = True,
351
- ) -> Tuple[torch.Tensor, ...]:
352
  """
353
- 前向传播
354
  """
355
  use_cache = use_cache if use_cache is not None else self.config.use_cache
356
 
357
  # 1. Embeddings
358
  hidden_states = self.embeddings(input_ids)
359
 
360
- # 2. Transformer
361
  present_key_values = () if use_cache else None
362
 
363
  for i, layer in enumerate(self.layers):
@@ -371,33 +384,38 @@ class FusionMini(PreTrainedModel):
371
  if use_cache:
372
  present_key_values = present_key_values + (cache,)
373
 
374
- # 4. 最后一层 Layer Norm
375
  hidden_states = self.ln_f(hidden_states)
376
 
377
  # 5. LM Head
378
  logits = self.lm_head(hidden_states)
379
 
380
- # 6. 计算损失(如果有 labels
381
  loss = None
382
  if labels is not None:
383
- # 移位:预测下一个 token
384
  shift_logits = logits[..., :-1, :].contiguous()
385
  shift_labels = labels[..., 1:].contiguous()
386
 
387
- # 交叉熵损失
388
  loss_fct = nn.CrossEntropyLoss()
389
  loss = loss_fct(
390
  shift_logits.view(-1, shift_logits.size(-1)),
391
  shift_labels.view(-1),
392
  )
393
 
394
- if use_cache:
395
- return {"loss": loss, "logits": logits, "past_key_values": present_key_values}
396
-
397
- if return_dict:
398
- return {"loss": loss, "logits": logits}
399
-
400
- return (loss, logits)
 
 
 
 
 
401
 
402
  @torch.no_grad()
403
  def generate(
@@ -434,8 +452,8 @@ class FusionMini(PreTrainedModel):
434
  past_key_values=past_key_values,
435
  )
436
 
437
- logits = outputs["logits"]
438
- past_key_values = outputs.get("past_key_values")
439
 
440
  next_token_logits = logits[:, -1, :] / temperature
441
 
@@ -518,8 +536,8 @@ if __name__ == "__main__":
518
  )
519
 
520
  print(f"\n[OK] 前向传播测试通过")
521
- print(f" Loss: {outputs['loss'].item():.4f}")
522
- print(f" Logits 形状: {outputs['logits'].shape}")
523
 
524
  # 测试生成
525
  generated = model.generate(
 
39
  import torch.nn as nn
40
  import torch.nn.functional as F
41
  from transformers import PretrainedConfig, PreTrainedModel
42
+ from transformers.modeling_outputs import CausalLMOutputWithPast
43
  from typing import Optional, Tuple
44
  import math
45
  import json
46
  from pathlib import Path
47
 
48
+ # H4-H6: Use try/except for relative imports with sys.path fallback
49
+ try:
50
+ from .sbla_attention import SBLAttention
51
+ from .fusion_model import RMSNorm
52
+ except ImportError:
53
+ from models.sbla_attention import SBLAttention
54
+ from models.fusion_model import RMSNorm
55
 
56
 
57
  class FusionMiniConfig(PretrainedConfig):
 
111
  raise FileNotFoundError(f"配置文件未找到:{config_file}")
112
 
113
 
114
+ # H1-H3: Register FusionMiniConfig with AutoConfig
115
+ try:
116
+ from transformers import AutoConfig
117
+ AutoConfig.register("fusion_mini", FusionMiniConfig)
118
+ except Exception:
119
+ pass # Already registered or AutoConfig unavailable
120
+
121
+
122
  class FusionMiniEmbeddings(nn.Module):
123
  """
124
  Fusion Mini 词嵌入
 
361
  past_key_values: Optional[Tuple] = None,
362
  use_cache: Optional[bool] = None,
363
  return_dict: Optional[bool] = True,
364
+ ) -> CausalLMOutputWithPast:
365
  """
366
+ Forward pass
367
  """
368
  use_cache = use_cache if use_cache is not None else self.config.use_cache
369
 
370
  # 1. Embeddings
371
  hidden_states = self.embeddings(input_ids)
372
 
373
+ # 2. Transformer layers
374
  present_key_values = () if use_cache else None
375
 
376
  for i, layer in enumerate(self.layers):
 
384
  if use_cache:
385
  present_key_values = present_key_values + (cache,)
386
 
387
+ # 4. Final Layer Norm
388
  hidden_states = self.ln_f(hidden_states)
389
 
390
  # 5. LM Head
391
  logits = self.lm_head(hidden_states)
392
 
393
+ # 6. Compute loss (if labels provided)
394
  loss = None
395
  if labels is not None:
396
+ # Shift: predict next token
397
  shift_logits = logits[..., :-1, :].contiguous()
398
  shift_labels = labels[..., 1:].contiguous()
399
 
400
+ # Cross-entropy loss
401
  loss_fct = nn.CrossEntropyLoss()
402
  loss = loss_fct(
403
  shift_logits.view(-1, shift_logits.size(-1)),
404
  shift_labels.view(-1),
405
  )
406
 
407
+ # C5: Return CausalLMOutputWithPast instead of plain dict
408
+ if not return_dict:
409
+ output = (logits,) + (present_key_values,) if present_key_values is not None else (logits,)
410
+ return ((loss,) + output) if loss is not None else output
411
+
412
+ return CausalLMOutputWithPast(
413
+ loss=loss,
414
+ logits=logits,
415
+ past_key_values=present_key_values,
416
+ hidden_states=None,
417
+ attentions=None,
418
+ )
419
 
420
  @torch.no_grad()
421
  def generate(
 
452
  past_key_values=past_key_values,
453
  )
454
 
455
+ logits = outputs.logits
456
+ past_key_values = outputs.past_key_values
457
 
458
  next_token_logits = logits[:, -1, :] / temperature
459
 
 
536
  )
537
 
538
  print(f"\n[OK] 前向传播测试通过")
539
+ print(f" Loss: {outputs.loss.item():.4f}")
540
+ print(f" Logits shape: {outputs.logits.shape}")
541
 
542
  # 测试生成
543
  generated = model.generate(
models/fusion_model.py CHANGED
@@ -39,9 +39,15 @@ import torch
39
  import torch.nn as nn
40
  import torch.nn.functional as F
41
  from transformers import PretrainedConfig, PreTrainedModel, GenerationMixin
 
42
  from typing import Optional, Tuple, Dict, Any
43
  import math
44
- from models.sbla_attention import SBLAttention
 
 
 
 
 
45
 
46
 
47
  class FusionConfig(PretrainedConfig):
@@ -65,13 +71,13 @@ class FusionConfig(PretrainedConfig):
65
  rms_norm_eps: float = 1e-6,
66
  use_cache: bool = True,
67
  tie_word_embeddings: bool = False,
68
- # SBLA 参数
69
  block_size: int = 512,
70
  latent_dim: int = 64,
71
  sbla_window_size: Optional[int] = None,
72
- window_size: Optional[int] = None, # Alias for sbla_window_size (for HF compatibility)
73
  sbla_mode: str = "pure_sbla",
74
- # Thinking Dial 参数
75
  enable_thinking_dial: bool = True,
76
  num_thinking_depths: int = 4,
77
  **kwargs,
@@ -93,16 +99,73 @@ class FusionConfig(PretrainedConfig):
93
  self.use_cache = use_cache
94
  self.tie_word_embeddings = tie_word_embeddings
95
 
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 参数
104
  self.enable_thinking_dial = enable_thinking_dial
105
  self.num_thinking_depths = num_thinking_depths
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
 
108
  class RMSNorm(nn.Module):
@@ -145,6 +208,21 @@ class FusionAttention(nn.Module):
145
  mode=mode,
146
  num_key_value_heads=config.num_key_value_heads,
147
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  def forward(
150
  self,
@@ -152,11 +230,34 @@ class FusionAttention(nn.Module):
152
  attention_mask: Optional[torch.Tensor] = None,
153
  past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
154
  use_cache: bool = False,
 
155
  ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  # S1 FIXED: KV Cache now works natively through SBLAttention.
157
- # SBLAttention handles past_key_value concatenation internally.
158
- output, present_key_value = self.sbla(
159
- hidden_states, attention_mask,
 
160
  past_key_value=past_key_value,
161
  use_cache=use_cache,
162
  )
@@ -255,7 +356,7 @@ class FusionModel(PreTrainedModel, GenerationMixin):
255
  use_cache: Optional[bool] = None,
256
  return_dict: Optional[bool] = True,
257
  **kwargs,
258
- ) -> Dict[str, Any]:
259
  use_cache = use_cache if use_cache is not None else self.config.use_cache
260
 
261
  # Embeddings
@@ -267,16 +368,6 @@ class FusionModel(PreTrainedModel, GenerationMixin):
267
  else:
268
  raise ValueError("Either input_ids or inputs_embeds must be provided")
269
 
270
- # 处理 attention_mask - pass raw HF format (1=valid, 0=padding) to SBLA
271
- # SBLAttention handles the conversion internally
272
- # DO NOT convert here - it would cause double-conversion NaN (F1)
273
- # if attention_mask is not None:
274
- # if attention_mask.dim() == 2:
275
- # attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
276
- # float_mask = attention_mask.to(dtype=hidden_states.dtype)
277
- # attention_mask = (1.0 - float_mask) * torch.finfo(hidden_states.dtype).min
278
-
279
- # Transformer 层(支持 KV Cache)
280
  # Use the already-resolved use_cache from parameter, don't re-override from kwargs
281
  if past_key_values is not None:
282
  use_cache = True
@@ -301,7 +392,7 @@ class FusionModel(PreTrainedModel, GenerationMixin):
301
  # LM Head
302
  logits = self.lm_head(hidden_states)
303
 
304
- # 损失
305
  loss = None
306
  if labels is not None:
307
  shift_logits = logits[..., :-1, :].contiguous()
@@ -309,13 +400,18 @@ class FusionModel(PreTrainedModel, GenerationMixin):
309
  loss_fct = nn.CrossEntropyLoss()
310
  loss = loss_fct(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1))
311
 
312
- if use_cache:
313
- return {"loss": loss, "logits": logits, "past_key_values": present_key_values}
314
-
315
  if not return_dict:
316
- return (loss, logits) if loss is not None else (logits,)
317
-
318
- return {"loss": loss, "logits": logits}
 
 
 
 
 
 
 
319
 
320
  @torch.no_grad()
321
  def generate(
@@ -350,8 +446,8 @@ class FusionModel(PreTrainedModel, GenerationMixin):
350
  return_dict=True,
351
  )
352
 
353
- logits = outputs["logits"]
354
- past_key_values = outputs.get("past_key_values", None)
355
 
356
  next_token_logits = logits[:, -1, :] / max(temperature, 1e-8)
357
 
@@ -408,8 +504,8 @@ if __name__ == "__main__":
408
 
409
  outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids, return_dict=True)
410
 
411
- assert outputs["loss"] is not None, "Loss should not be None"
412
- assert not torch.isnan(outputs["loss"]).item(), "Loss is NaN!"
413
- print(f"Loss={outputs['loss'].item():.4f}, Logits={outputs['logits'].shape}")
414
 
415
  print("\n[ALL TESTS PASSED] Fusion Model v2 fully functional.")
 
39
  import torch.nn as nn
40
  import torch.nn.functional as F
41
  from transformers import PretrainedConfig, PreTrainedModel, GenerationMixin
42
+ from transformers.modeling_outputs import CausalLMOutputWithPast
43
  from typing import Optional, Tuple, Dict, Any
44
  import math
45
+
46
+ # H4-H6: Use try/except for relative imports with sys.path fallback
47
+ try:
48
+ from .sbla_attention import SBLAttention
49
+ except ImportError:
50
+ from models.sbla_attention import SBLAttention
51
 
52
 
53
  class FusionConfig(PretrainedConfig):
 
71
  rms_norm_eps: float = 1e-6,
72
  use_cache: bool = True,
73
  tie_word_embeddings: bool = False,
74
+ # SBLA parameters
75
  block_size: int = 512,
76
  latent_dim: int = 64,
77
  sbla_window_size: Optional[int] = None,
78
+ window_size: Optional[int] = None,
79
  sbla_mode: str = "pure_sbla",
80
+ # Thinking Dial parameters
81
  enable_thinking_dial: bool = True,
82
  num_thinking_depths: int = 4,
83
  **kwargs,
 
99
  self.use_cache = use_cache
100
  self.tie_word_embeddings = tie_word_embeddings
101
 
102
+ # SBLA parameters
103
  self.block_size = block_size
104
  self.latent_dim = latent_dim
105
  self.window_size = window_size or sbla_window_size or block_size
106
+ self.sbla_window_size = self.window_size
107
  self.sbla_mode = sbla_mode
108
 
109
+ # Thinking Dial parameters
110
  self.enable_thinking_dial = enable_thinking_dial
111
  self.num_thinking_depths = num_thinking_depths
112
+
113
+ # RoPE parameters
114
+ self.rope_theta = kwargs.pop('rope_theta', 10000.0)
115
+
116
+
117
+ # H1-H3: Register FusionConfig with AutoConfig
118
+ try:
119
+ from transformers import AutoConfig
120
+ AutoConfig.register("fusion", FusionConfig)
121
+ except Exception:
122
+ pass # Already registered or AutoConfig unavailable
123
+
124
+
125
+ class RotaryEmbedding(nn.Module):
126
+ """Rotary Position Embedding (RoPE) for positional encoding in attention."""
127
+
128
+ def __init__(self, dim, max_position_embeddings=2048, base=10000.0):
129
+ super().__init__()
130
+ self.dim = dim
131
+ self.max_position_embeddings = max_position_embeddings
132
+ self.base = base
133
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
134
+ self.register_buffer('inv_freq', inv_freq)
135
+
136
+ def forward(self, seq_len, device=None):
137
+ t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
138
+ freqs = torch.outer(t, self.inv_freq)
139
+ emb = torch.cat((freqs, freqs), dim=-1)
140
+ return emb
141
+
142
+
143
+ def rotate_half(x):
144
+ """Rotate half the hidden dims of the input."""
145
+ x1 = x[..., :x.shape[-1] // 2]
146
+ x2 = x[..., x.shape[-1] // 2:]
147
+ return torch.cat((-x2, x1), dim=-1)
148
+
149
+
150
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None):
151
+ """Apply rotary position embedding to query and key tensors.
152
+
153
+ Args:
154
+ q: (batch, num_heads, seq_len, head_dim)
155
+ k: (batch, num_heads, seq_len, head_dim) or (batch, num_kv_heads, seq_len, head_dim)
156
+ cos: (seq_len, head_dim) cosine part of rotary embedding
157
+ sin: (seq_len, head_dim) sine part of rotary embedding
158
+ position_ids: optional position ids (unused, for API compat)
159
+
160
+ Returns:
161
+ Tuple of (q_embed, k_embed) with rotary position encoding applied.
162
+ """
163
+ # cos/sin: (seq_len, head_dim) -> (1, 1, seq_len, head_dim)
164
+ cos = cos.unsqueeze(0).unsqueeze(0)
165
+ sin = sin.unsqueeze(0).unsqueeze(0)
166
+ q_embed = (q * cos) + (rotate_half(q) * sin)
167
+ k_embed = (k * cos) + (rotate_half(k) * sin)
168
+ return q_embed, k_embed
169
 
170
 
171
  class RMSNorm(nn.Module):
 
208
  mode=mode,
209
  num_key_value_heads=config.num_key_value_heads,
210
  )
211
+
212
+ # M14 FIX: Add RoPE - Rotary Position Embedding
213
+ head_dim = config.hidden_size // config.num_attention_heads
214
+ rope_theta = getattr(config, 'rope_theta', 10000.0)
215
+ self.rotary_emb = RotaryEmbedding(
216
+ dim=head_dim,
217
+ max_position_embeddings=config.max_position_embeddings,
218
+ base=rope_theta,
219
+ )
220
+
221
+ # Separate Q/K/V projections for RoPE application
222
+ # (SBLAttention has its own q/k/v, but we need access before RoPE)
223
+ self.q_proj = nn.Linear(config.hidden_size, config.num_attention_heads * head_dim, bias=False)
224
+ self.k_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * head_dim, bias=False)
225
+ self.v_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * head_dim, bias=False)
226
 
227
  def forward(
228
  self,
 
230
  attention_mask: Optional[torch.Tensor] = None,
231
  past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
232
  use_cache: bool = False,
233
+ position_ids: Optional[torch.Tensor] = None,
234
  ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
235
+ # M14 FIX: Apply RoPE to Q and K before SBLAttention
236
+ batch_size, seq_len, _ = hidden_states.shape
237
+ head_dim = self.sbla.head_dim
238
+ num_kv_groups = self.sbla.num_kv_groups
239
+
240
+ # Project Q/K/V
241
+ Q = self.q_proj(hidden_states).view(batch_size, seq_len, self.sbla.num_heads, head_dim).transpose(1, 2)
242
+ K = self.k_proj(hidden_states).view(batch_size, seq_len, self.sbla.num_key_value_heads, head_dim).transpose(1, 2)
243
+ V = self.v_proj(hidden_states).view(batch_size, seq_len, self.sbla.num_key_value_heads, head_dim).transpose(1, 2)
244
+
245
+ # Compute RoPE embeddings
246
+ kv_seq_len = seq_len
247
+ if past_key_value is not None:
248
+ kv_seq_len = past_key_value[0].shape[2] + seq_len
249
+ emb = self.rotary_emb(kv_seq_len, device=hidden_states.device)
250
+ cos = emb.cos()
251
+ sin = emb.sin()
252
+ # Apply RoPE to Q (full position range) and K (full position range)
253
+ Q, K = apply_rotary_pos_emb(Q, K, cos, sin)
254
+
255
+ # Store RoPE'd K/V in SBLAttention's cache for incremental generation
256
  # S1 FIXED: KV Cache now works natively through SBLAttention.
257
+ # We pass the RoPE'd Q/K/V by injecting them into SBLAttention.
258
+ output, present_key_value = self.sbla.forward_with_qkv(
259
+ Q, K, V,
260
+ attention_mask,
261
  past_key_value=past_key_value,
262
  use_cache=use_cache,
263
  )
 
356
  use_cache: Optional[bool] = None,
357
  return_dict: Optional[bool] = True,
358
  **kwargs,
359
+ ) -> CausalLMOutputWithPast:
360
  use_cache = use_cache if use_cache is not None else self.config.use_cache
361
 
362
  # Embeddings
 
368
  else:
369
  raise ValueError("Either input_ids or inputs_embeds must be provided")
370
 
 
 
 
 
 
 
 
 
 
 
371
  # Use the already-resolved use_cache from parameter, don't re-override from kwargs
372
  if past_key_values is not None:
373
  use_cache = True
 
392
  # LM Head
393
  logits = self.lm_head(hidden_states)
394
 
395
+ # Loss
396
  loss = None
397
  if labels is not None:
398
  shift_logits = logits[..., :-1, :].contiguous()
 
400
  loss_fct = nn.CrossEntropyLoss()
401
  loss = loss_fct(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1))
402
 
403
+ # C2/C3/C5: Return CausalLMOutputWithPast instead of plain dict
 
 
404
  if not return_dict:
405
+ output = (logits,) + (present_key_values,) if present_key_values is not None else (logits,)
406
+ return ((loss,) + output) if loss is not None else output
407
+
408
+ return CausalLMOutputWithPast(
409
+ loss=loss,
410
+ logits=logits,
411
+ past_key_values=present_key_values,
412
+ hidden_states=None,
413
+ attentions=None,
414
+ )
415
 
416
  @torch.no_grad()
417
  def generate(
 
446
  return_dict=True,
447
  )
448
 
449
+ logits = outputs.logits
450
+ past_key_values = outputs.past_key_values
451
 
452
  next_token_logits = logits[:, -1, :] / max(temperature, 1e-8)
453
 
 
504
 
505
  outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids, return_dict=True)
506
 
507
+ assert outputs.loss is not None, "Loss should not be None"
508
+ assert not torch.isnan(outputs.loss).item(), "Loss is NaN!"
509
+ print(f"Loss={outputs.loss.item():.4f}, Logits={outputs.logits.shape}")
510
 
511
  print("\n[ALL TESTS PASSED] Fusion Model v2 fully functional.")
models/optimized_sbla_attention.py CHANGED
@@ -3,7 +3,7 @@
3
  """
4
  import sys
5
  import torch
6
- import torch.nn as F
7
  from pathlib import Path
8
  from typing import Optional, Tuple
9
 
 
3
  """
4
  import sys
5
  import torch
6
+ import torch.nn.functional as F
7
  from pathlib import Path
8
  from typing import Optional, Tuple
9
 
models/sbla_attention.py CHANGED
@@ -158,15 +158,18 @@ class SBLAttention(nn.Module):
158
  device: torch.device,
159
  ) -> torch.Tensor:
160
  """
161
- 构建滑动窗口掩码(支持非正方形,用于 KV cache
162
 
163
- 每个 token 只能看到前后 window_size 范围内的 token
 
 
164
  """
 
165
  q_pos = torch.arange(q_len, device=device).float() + (kv_len - q_len)
166
  kv_pos = torch.arange(kv_len, device=device).float()
167
  distance = torch.abs(q_pos.unsqueeze(1) - kv_pos.unsqueeze(0))
168
 
169
- mask = (distance > window_size).float()
170
  return mask.masked_fill(mask.bool(), float('-inf'))
171
 
172
  def _compute_block_latents(
@@ -193,7 +196,8 @@ class SBLAttention(nn.Module):
193
  num_blocks = math.ceil(seq_len / self.block_size)
194
  padded_len = num_blocks * self.block_size
195
 
196
- # Padding(如果需要)
 
197
  if padded_len > seq_len:
198
  pad_len = padded_len - seq_len
199
  hidden_states_padded = F.pad(hidden_states, (0, 0, 0, pad_len))
@@ -433,6 +437,145 @@ class SBLAttention(nn.Module):
433
 
434
  return output, present_key_value
435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
  # 别名(兼容旧代码)
438
  SlidingBlockLatentAttention = SBLAttention
 
158
  device: torch.device,
159
  ) -> torch.Tensor:
160
  """
161
+ Build sliding window mask (supports non-square, for KV cache)
162
 
163
+ Each token can only attend to tokens within window_size range.
164
+ H7: Clamp window_size to kv_len to avoid degenerate masks when
165
+ window_size >= sequence length.
166
  """
167
+ effective_window = min(window_size, kv_len)
168
  q_pos = torch.arange(q_len, device=device).float() + (kv_len - q_len)
169
  kv_pos = torch.arange(kv_len, device=device).float()
170
  distance = torch.abs(q_pos.unsqueeze(1) - kv_pos.unsqueeze(0))
171
 
172
+ mask = (distance > effective_window).float()
173
  return mask.masked_fill(mask.bool(), float('-inf'))
174
 
175
  def _compute_block_latents(
 
196
  num_blocks = math.ceil(seq_len / self.block_size)
197
  padded_len = num_blocks * self.block_size
198
 
199
+ # H7: Handle remainder when seq_len is not divisible by block_size
200
+ # We pad the last block so all blocks are uniform size for matrix ops
201
  if padded_len > seq_len:
202
  pad_len = padded_len - seq_len
203
  hidden_states_padded = F.pad(hidden_states, (0, 0, 0, pad_len))
 
437
 
438
  return output, present_key_value
439
 
440
+ def forward_with_qkv(
441
+ self,
442
+ Q: torch.Tensor,
443
+ K: torch.Tensor,
444
+ V: torch.Tensor,
445
+ attention_mask: Optional[torch.Tensor] = None,
446
+ past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
447
+ use_cache: bool = False,
448
+ ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
449
+ """Forward pass with pre-projected Q/K/V (e.g., after RoPE application).
450
+
451
+
452
+ This allows external position encoding (like RoPE) to be applied to Q/K
453
+ before entering the SBLA attention computation.
454
+
455
+ Args:
456
+ Q: (batch, num_heads, seq_len, head_dim) - already with position encoding
457
+ K: (batch, num_kv_heads, seq_len, head_dim) - already with position encoding
458
+ V: (batch, num_kv_heads, seq_len, head_dim)
459
+ attention_mask: attention mask
460
+ past_key_value: cached (K, V) from previous steps
461
+ use_cache: whether to return cache
462
+
463
+ Returns:
464
+ (output, present_key_value)
465
+ """
466
+ batch_size, num_heads, seq_len, head_dim = Q.shape
467
+ device = Q.device
468
+
469
+ # KV Cache: concatenate with past
470
+ kv_seq_len = seq_len
471
+ if past_key_value is not None:
472
+ past_K, past_V = past_key_value
473
+ kv_seq_len = past_K.shape[2] + seq_len
474
+ K = torch.cat([past_K, K], dim=2)
475
+ V = torch.cat([past_V, V], dim=2)
476
+
477
+ present_key_value = (K, V) if use_cache else None
478
+
479
+ # GQA: expand K/V to match Q heads
480
+ K = self._repeat_kv(K, self.num_kv_groups)
481
+ V = self._repeat_kv(V, self.num_kv_groups)
482
+
483
+ # Build masks
484
+ causal_mask = self._build_causal_mask(seq_len, kv_seq_len, device)
485
+
486
+ if self.mode == "pure_sbla":
487
+ window_mask = self._build_window_mask(seq_len, kv_seq_len, self.window_size, device)
488
+ combined_mask = causal_mask + window_mask
489
+ else:
490
+ combined_mask = causal_mask
491
+
492
+ # Apply external attention_mask (padding)
493
+ if attention_mask is not None:
494
+ if attention_mask.dim() == 2:
495
+ if past_key_value is not None:
496
+ full_mask = torch.ones(batch_size, kv_seq_len, device=device, dtype=attention_mask.dtype)
497
+ full_mask[:, -seq_len:] = attention_mask
498
+ padding_mask = (1.0 - full_mask.float()).unsqueeze(1).unsqueeze(2)
499
+ else:
500
+ padding_mask = (1.0 - attention_mask.float()).unsqueeze(1).unsqueeze(2)
501
+ padding_mask = padding_mask * torch.finfo(Q.dtype).min
502
+ combined_mask = combined_mask.unsqueeze(0).unsqueeze(0) + padding_mask
503
+ elif attention_mask.dim() == 4:
504
+ ext_mask = attention_mask.squeeze(1)
505
+ if past_key_value is not None:
506
+ full_mask = torch.ones(batch_size, 1, kv_seq_len, device=device, dtype=ext_mask.dtype)
507
+ full_mask[:, :, -seq_len:] = ext_mask
508
+ padding_mask = (1.0 - full_mask) * float('-inf')
509
+ else:
510
+ padding_mask = (1.0 - ext_mask) * float('-inf')
511
+ combined_mask = combined_mask.unsqueeze(0) + padding_mask.unsqueeze(1)
512
+ else:
513
+ padding_mask = (1.0 - attention_mask.float()).unsqueeze(1)
514
+ if past_key_value is not None:
515
+ full_mask = torch.ones(batch_size, 1, 1, kv_seq_len, device=device, dtype=attention_mask.dtype)
516
+ full_mask[:, :, :, -seq_len:] = attention_mask.unsqueeze(1)
517
+ padding_mask = (1.0 - full_mask.float()) * torch.finfo(Q.dtype).min
518
+ else:
519
+ padding_mask = padding_mask * torch.finfo(Q.dtype).min
520
+ combined_mask = combined_mask.unsqueeze(0).unsqueeze(0) + padding_mask
521
+ else:
522
+ combined_mask = combined_mask.unsqueeze(0)
523
+
524
+ # Compute attention
525
+ attn_scores = torch.matmul(Q, K.transpose(-1, -2)) / math.sqrt(self.head_dim)
526
+ attn_scores = attn_scores + combined_mask
527
+
528
+ attn_probs = F.softmax(attn_scores, dim=-1)
529
+ attn_probs = self.dropout(attn_probs)
530
+
531
+ context = torch.matmul(attn_probs, V)
532
+ context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_size)
533
+ output_std = self.out_proj(context)
534
+
535
+ # SBLA latent contribution (skip for incremental steps)
536
+ if past_key_value is not None and seq_len <= 1:
537
+ output = output_std
538
+ output = self.LayerNorm(output)
539
+ output = self.dropout(output)
540
+ return output, present_key_value
541
+
542
+ # Reconstruct hidden_states from V for block latent computation
543
+ V_full = self._repeat_kv(V, self.num_kv_groups) if self.num_kv_groups > 1 else V
544
+ hidden_states_approx = V_full.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_size)
545
+
546
+ latent_mask = attention_mask
547
+ if attention_mask is not None and attention_mask.dim() == 2:
548
+ latent_mask = attention_mask.unsqueeze(1).unsqueeze(2)
549
+
550
+ (
551
+ blk_q, blk_k, blk_v,
552
+ num_blocks, real_block_sizes,
553
+ ) = self._compute_block_latents(hidden_states_approx, latent_mask)
554
+
555
+ latent_causal_mask = self._build_causal_mask(num_blocks, num_blocks, device)
556
+ latent_attn_scores = torch.matmul(blk_q, blk_k.transpose(-1, -2)) / math.sqrt(self.latent_dim)
557
+ latent_attn_scores = latent_attn_scores + latent_causal_mask.unsqueeze(0)
558
+
559
+ latent_attn_probs = F.softmax(latent_attn_scores, dim=-1)
560
+ latent_attn_probs = self.dropout(latent_attn_probs)
561
+
562
+ latent_context = torch.matmul(latent_attn_probs, blk_v)
563
+ latent_output = self.latent_out_proj(latent_context)
564
+
565
+ latent_output = latent_output.unsqueeze(2).expand(
566
+ -1, -1, self.block_size, -1
567
+ ).contiguous().view(batch_size, num_blocks * self.block_size, self.hidden_size)
568
+
569
+ latent_output = latent_output[:, :seq_len, :]
570
+
571
+ gate_value = torch.sigmoid(self.gate)
572
+ output = output_std + gate_value * latent_output
573
+
574
+ output = self.LayerNorm(output)
575
+ output = self.dropout(output)
576
+
577
+ return output, present_key_value
578
+
579
 
580
  # 别名(兼容旧代码)
581
  SlidingBlockLatentAttention = SBLAttention
models/thinking_dial.py CHANGED
@@ -173,11 +173,12 @@ class ThinkingDialProcessor:
173
 
174
  def _register_special_tokens(self):
175
  """注册特殊 token 到 tokenizer"""
 
 
 
 
176
  special_tokens = {
177
- "additional_special_tokens": [
178
- THINK_START,
179
- THINK_END,
180
- ]
181
  }
182
 
183
  num_added = self.tokenizer.add_special_tokens(special_tokens)
@@ -617,7 +618,7 @@ class GRPOTrainer:
617
 
618
  # Step 4: Get log probs and compute GRPO loss
619
  outputs = self.model(input_ids=generated_ids)
620
- logits = outputs["logits"]
621
 
622
  use_labels = labels.repeat_interleave(num_samples, dim=0) if labels is not None else generated_ids
623
  log_probs = self._normalize_logits_to_log_probs(logits, use_labels)
 
173
 
174
  def _register_special_tokens(self):
175
  """注册特殊 token 到 tokenizer"""
176
+ # M1-M3 FIX: Register complete think tokens matching tokenizer.py format
177
+ # tokenizer.py registers ["<|think_depth_0|>", "<|think_depth_1|>", "<|think_depth_2|>", "<|think_depth_3|>"]
178
+ think_tokens = [build_think_token(d) for d in range(4)] # ["<|think_depth_0|>", ..., "<|think_depth_3|>"]
179
+
180
  special_tokens = {
181
+ "additional_special_tokens": think_tokens,
 
 
 
182
  }
183
 
184
  num_added = self.tokenizer.add_special_tokens(special_tokens)
 
618
 
619
  # Step 4: Get log probs and compute GRPO loss
620
  outputs = self.model(input_ids=generated_ids)
621
+ logits = outputs.logits if hasattr(outputs, 'logits') else outputs['logits']
622
 
623
  use_labels = labels.repeat_interleave(num_samples, dim=0) if labels is not None else generated_ids
624
  log_probs = self._normalize_logits_to_log_probs(logits, use_labels)
tokenizers/char_vocab.txt ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [PAD]
2
+ [UNK]
3
+ [CLS]
4
+
5
+
6
+
7
+ .
8
+ A
9
+ B
10
+ C
11
+ F
12
+ H
13
+ I
14
+ L
15
+ M
16
+ S
17
+ T
18
+ W
19
+ a
20
+ b
21
+ c
22
+ d
23
+ e
24
+ f
25
+ g
26
+ h
27
+ i
28
+ j
29
+ k
30
+ l
31
+ m
32
+ n
33
+ o
34
+ p
35
+ r
36
+ s
37
+ t
38
+ u
39
+ v
40
+ w
41
+ y
train/dpo_finetune.py CHANGED
@@ -35,7 +35,13 @@ import torch.nn.functional as F
35
  PROJECT_ROOT = Path(__file__).parent.parent
36
  sys.path.insert(0, str(PROJECT_ROOT))
37
 
38
- from models.fusion_model import FusionModel, FusionConfig
 
 
 
 
 
 
39
 
40
 
41
  @dataclass
 
35
  PROJECT_ROOT = Path(__file__).parent.parent
36
  sys.path.insert(0, str(PROJECT_ROOT))
37
 
38
+ # H4-H6: Use try/except for imports with sys.path fallback
39
+ try:
40
+ from models.fusion_model import FusionModel, FusionConfig
41
+ except ImportError:
42
+ # Fallback: add project root and retry
43
+ sys.path.insert(0, str(PROJECT_ROOT))
44
+ from models.fusion_model import FusionModel, FusionConfig
45
 
46
 
47
  @dataclass
train/lora_finetune.py CHANGED
@@ -35,10 +35,26 @@ from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
35
  import sys
36
  import os
37
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  # 添加项目根目录到路径
39
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
40
 
41
- from models import FusionModel, FusionConfig
 
 
 
 
42
  import json
43
  import logging
44
 
@@ -188,13 +204,26 @@ def create_local_model(
188
  total_params = sum(p.numel() for p in model.parameters())
189
  logger.info(f"[create_local_model] 模型参数总量:{total_params / 1e9:.2f}B")
190
 
191
- # 量化处理
 
 
 
192
  if quantize:
193
  if load_in_4bit:
194
- logger.info("[create_local_model] 使用 4-bit 量化(QLoRA")
 
 
 
 
 
 
 
 
 
 
195
  model = prepare_model_for_kbit_training(model)
196
  elif load_in_8bit:
197
- logger.info("[create_local_model] 使用 8-bit 量化")
198
  model = prepare_model_for_kbit_training(model)
199
 
200
  return model, config
 
35
  import sys
36
  import os
37
 
38
+ # H8-H9: Wrap optional imports in try/except
39
+ try:
40
+ import deepspeed
41
+ except ImportError:
42
+ deepspeed = None
43
+ logging.warning("DeepSpeed not installed. DeepSpeed features will be unavailable.")
44
+
45
+ try:
46
+ import bitsandbytes
47
+ except ImportError:
48
+ bitsandbytes = None
49
+
50
  # 添加项目根目录到路径
51
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
52
 
53
+ # H4-H6: Use try/except for project imports
54
+ try:
55
+ from models import FusionModel, FusionConfig
56
+ except ImportError:
57
+ from models.fusion_model import FusionModel, FusionConfig
58
  import json
59
  import logging
60
 
 
204
  total_params = sum(p.numel() for p in model.parameters())
205
  logger.info(f"[create_local_model] 模型参数总量:{total_params / 1e9:.2f}B")
206
 
207
+ # C4: Fix quantization - use prepare_model_for_kbit_training for local models
208
+ # For loading from HF hub with QLoRA, BitsAndBytesConfig would be used with
209
+ # AutoModelForCausalLM.from_pretrained. Since we create local models, we use
210
+ # prepare_model_for_kbit_training after model creation.
211
  if quantize:
212
  if load_in_4bit:
213
+ logger.info("[create_local_model] Using 4-bit quantization (QLoRA)")
214
+ try:
215
+ from transformers import BitsAndBytesConfig
216
+ bnb_config = BitsAndBytesConfig(
217
+ load_in_4bit=True,
218
+ bnb_4bit_quant_type="nf4",
219
+ bnb_4bit_use_double_quant=True,
220
+ )
221
+ logger.info("[create_local_model] BitsAndBytesConfig created for NF4 quantization")
222
+ except ImportError:
223
+ logger.warning("bitsandbytes not installed, 4-bit quantization may not work properly")
224
  model = prepare_model_for_kbit_training(model)
225
  elif load_in_8bit:
226
+ logger.info("[create_local_model] Using 8-bit quantization")
227
  model = prepare_model_for_kbit_training(model)
228
 
229
  return model, config
train/train_mini.py CHANGED
@@ -156,6 +156,17 @@ def train_mini_model(
156
  max_length: 最大序列长度
157
  device: 设备
158
  """
 
 
 
 
 
 
 
 
 
 
 
159
  print("=" * 60)
160
  print("Fusion Mini 训练脚本")
161
  print("=" * 60)
 
156
  max_length: 最大序列长度
157
  device: 设备
158
  """
159
+ # Set deterministic seeds for reproducibility
160
+ import random
161
+ import numpy as np
162
+ random.seed(42)
163
+ np.random.seed(42)
164
+ torch.manual_seed(42)
165
+ if torch.cuda.is_available():
166
+ torch.cuda.manual_seed_all(42)
167
+ torch.backends.cudnn.deterministic = True
168
+ torch.backends.cudnn.benchmark = False
169
+
170
  print("=" * 60)
171
  print("Fusion Mini 训练脚本")
172
  print("=" * 60)
train/train_real.py CHANGED
@@ -110,8 +110,13 @@ def train_real():
110
  else:
111
  encoded = encoded + [0] * (seq_len - len(encoded))
112
 
113
- batch_input.append(encoded[:-1]) # 输入:除最后一个 token
114
- batch_labels.append(encoded[1:]) # 标签:除第一个 token
 
 
 
 
 
115
 
116
  input_ids = torch.tensor(batch_input)
117
  labels = torch.tensor(batch_labels)
 
110
  else:
111
  encoded = encoded + [0] * (seq_len - len(encoded))
112
 
113
+ # M4-M5 FIX: Do NOT pre-shift labels here.
114
+ # The model's forward() already applies the shift internally:
115
+ # shift_logits = logits[..., :-1, :]
116
+ # shift_labels = labels[..., 1:]
117
+ # Pre-shifting here would cause a double-shift bug.
118
+ batch_input.append(encoded) # Full sequence as input
119
+ batch_labels.append(encoded) # Full sequence as labels (model handles shift)
120
 
121
  input_ids = torch.tensor(batch_input)
122
  labels = torch.tensor(batch_labels)