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

fix: v13 external audit - CRITICAL/HIGH/MEDIUM fixes

Browse files

CRITICAL (3):
- F-NEW-8: Fix quantization_tool.py import errors (DyQuant -> DyQuantConverter)
- F-NEW-9: Fix DyQuant/QATTrainer constructor signature mismatches
- F-NEW-10: Fix QATTrainer constructor parameter mismatch

HIGH (4):
- S-NEW-8: Remove unused Q/K/V projections from SBLAttention (~1.6B params saved)
- S-NEW-9: Implement real bitsandbytes 4/8-bit quantization (was fake QLoRA)
- S-NEW-10: Replace DataCollatorForSeq2Seq with DataCollatorForLanguageModeling
- S-NEW-11: Fix single-file model export loading actual weights (was random)

MEDIUM:
- M-NEW-11: Initialize _tokenizer in DPOTrainer.__init__
- M-NEW-13: Remove dead code (optimized_sbla_attention.py - never used)

Verified: all files pass py_compile syntax check

evaluation/quantization_tool.py CHANGED
@@ -19,7 +19,7 @@ from torch.quantization import get_default_qconfig, prepare_qat, convert
19
  sys.path.insert(0, '.')
20
 
21
  from evaluation.metrics import ModelEvaluator, EvaluationMetrics
22
- from inference.dyquant import DyQuant, QATTrainer, QuantizedLinear
23
 
24
 
25
  class QuantizationTool:
@@ -30,6 +30,7 @@ class QuantizationTool:
30
  model: nn.Module,
31
  tokenizer = None,
32
  device: str = "cpu",
 
33
  ):
34
  """
35
  初始化量化工具
@@ -38,21 +39,22 @@ class QuantizationTool:
38
  model: 要量化的模型
39
  tokenizer: tokenizer(可选)
40
  device: 设备
 
41
  """
42
  self.model = model
43
  self.tokenizer = tokenizer
44
  self.device = device
 
45
  self.original_model = None
46
  self.quantized_model = None
47
  self.qat_trainer = None
 
48
 
49
  def backup_original_model(self):
50
  """备份原始模型"""
51
  print("[QuantTool] 备份原始模型...")
52
- self.original_model = type(self.model)(
53
- self.model.config if hasattr(self.model, 'config') else None
54
- )
55
- self.original_model.load_state_dict(self.model.state_dict())
56
  print("[QuantTool] 备份完成")
57
 
58
  def dynamic_quantize(
@@ -72,31 +74,33 @@ class QuantizationTool:
72
  """
73
  print(f"[QuantTool] 开始动态量化({bits}-bit, {mode})...")
74
 
75
- dyquant = DyQuant(
76
- model=self.model,
77
- config=DyQuantConfig(
78
- bits=bits,
79
- symmetric=(mode == "symmetric"),
80
- mixed_precision=False,
81
- ),
82
  )
83
 
84
- self.quantized_model = dyquant.convert()
85
- print(f"[QuantTool] 动态量化完成")
 
86
 
 
87
  return self.quantized_model
88
 
89
  def prepare_qat(
90
  self,
91
  learning_rate: float = 1e-4,
92
  num_epochs: int = 3,
 
93
  ) -> "QATTrainer":
94
  """
95
  准备量化感知训练(QAT)
96
 
97
  参数:
98
  learning_rate: 学习率
99
- num_epochs: 训练轮数
 
100
 
101
  返回:
102
  QATTrainer 对象
@@ -105,17 +109,42 @@ class QuantizationTool:
105
  print(f" 学习率: {learning_rate}")
106
  print(f" 训练轮数: {num_epochs}")
107
 
 
 
 
 
 
 
 
 
108
  self.qat_trainer = QATTrainer(
109
- model=self.model,
 
110
  learning_rate=learning_rate,
111
- num_epochs=num_epochs,
112
  )
113
 
 
 
 
114
  self.qat_trainer.prepare()
115
  print(f"[QuantTool] QAT 准备完成")
116
 
 
 
 
117
  return self.qat_trainer
118
 
 
 
 
 
 
 
 
 
 
 
119
  def evaluate_quantized(
120
  self,
121
  texts: List[str],
@@ -250,15 +279,7 @@ class QuantizationTool:
250
  if format == "safetensors":
251
  try:
252
  import safetensors.torch
253
- # 准备状态字典(处理 QuantizedLinear)
254
- state_dict = {}
255
- for name, module in self.quantized_model.named_modules():
256
- if isinstance(module, QuantizedLinear):
257
- state_dict[f"{name}.q_weight"] = module.q_weight
258
- state_dict[f"{name}.q_scale"] = module.q_scale
259
- state_dict[f"{name}.q_zero_point"] = module.q_zero_point
260
- state_dict[f"{name}.bias"] = module.bias
261
- safetensors.torch.save_file(state_dict, path)
262
  except ImportError:
263
  print(f"[QuantTool] 警告:safetensors 未安装,使用 PyTorch 格式")
264
  format = "pytorch"
@@ -269,20 +290,6 @@ class QuantizationTool:
269
  print(f"[QuantTool] 保存完成")
270
 
271
 
272
- class DyQuantConfig:
273
- """DyQuant 配置(简化版)"""
274
-
275
- def __init__(
276
- self,
277
- bits: int = 8,
278
- symmetric: bool = True,
279
- mixed_precision: bool = False,
280
- ):
281
- self.bits = bits
282
- self.symmetric = symmetric
283
- self.mixed_precision = mixed_precision
284
-
285
-
286
  def quantize_model(
287
  model: nn.Module,
288
  method: str = "dynamic",
@@ -307,7 +314,8 @@ def quantize_model(
307
  return tool.dynamic_quantize(bits=bits)
308
  elif method == "qat":
309
  trainer = tool.prepare_qat(**kwargs)
310
- trainer.train(**kwargs)
 
311
  return tool.quantized_model
312
  else:
313
  raise ValueError(f"不支持的量化方法: {method}")
@@ -324,6 +332,6 @@ if __name__ == "__main__":
324
  print()
325
  print("用法:")
326
  print(" from evaluation.quantization_tool import QuantizationTool")
327
- print(" tool = QuantizationTool(model)")
328
  print(" quantized_model = tool.dynamic_quantize(bits=8)")
329
  print(" metrics = tool.compare_models(texts)")
 
19
  sys.path.insert(0, '.')
20
 
21
  from evaluation.metrics import ModelEvaluator, EvaluationMetrics
22
+ from inference.dyquant import DyQuantConverter, QATTrainer, QuantConfig
23
 
24
 
25
  class QuantizationTool:
 
30
  model: nn.Module,
31
  tokenizer = None,
32
  device: str = "cpu",
33
+ model_path: Optional[str] = None,
34
  ):
35
  """
36
  初始化量化工具
 
39
  model: 要量化的模型
40
  tokenizer: tokenizer(可选)
41
  device: 设备
42
+ model_path: 模型路径(用于 DyQuant)
43
  """
44
  self.model = model
45
  self.tokenizer = tokenizer
46
  self.device = device
47
+ self.model_path = model_path or "fusion-mini"
48
  self.original_model = None
49
  self.quantized_model = None
50
  self.qat_trainer = None
51
+ self.converter = None
52
 
53
  def backup_original_model(self):
54
  """备份原始模型"""
55
  print("[QuantTool] 备份原始模型...")
56
+ import copy
57
+ self.original_model = copy.deepcopy(self.model)
 
 
58
  print("[QuantTool] 备份完成")
59
 
60
  def dynamic_quantize(
 
74
  """
75
  print(f"[QuantTool] 开始动态量化({bits}-bit, {mode})...")
76
 
77
+ # 使用正确的 QuantConfig API
78
+ config = QuantConfig(
79
+ model_path=self.model_path,
80
+ bits=bits,
81
+ mixed_precision=False,
 
 
82
  )
83
 
84
+ self.converter = DyQuantConverter(config)
85
+ self.converter.model = self.model # 注入已加载的模型
86
+ self.quantized_model = self.converter.convert()
87
 
88
+ print(f"[QuantTool] 动态量化完成")
89
  return self.quantized_model
90
 
91
  def prepare_qat(
92
  self,
93
  learning_rate: float = 1e-4,
94
  num_epochs: int = 3,
95
+ train_data: Optional[str] = None,
96
  ) -> "QATTrainer":
97
  """
98
  准备量化感知训练(QAT)
99
 
100
  参数:
101
  learning_rate: 学习率
102
+ num_epochs: 训练轮数(注意:QATTrainer 没有 num_epochs 参数)
103
+ train_data: 训练数据路径
104
 
105
  返回:
106
  QATTrainer 对象
 
109
  print(f" 学习率: {learning_rate}")
110
  print(f" 训练轮数: {num_epochs}")
111
 
112
+ # 使用正确的 QuantConfig API
113
+ config = QuantConfig(
114
+ model_path=self.model_path,
115
+ bits=4,
116
+ mixed_precision=True,
117
+ )
118
+
119
+ # QATTrainer 签名:(config, train_data, learning_rate, warmup_steps)
120
  self.qat_trainer = QATTrainer(
121
+ config=config,
122
+ train_data=train_data,
123
  learning_rate=learning_rate,
124
+ warmup_steps=100,
125
  )
126
 
127
+ # 注入已加载的模型,避免重复加载
128
+ self.qat_trainer.model = self.model
129
+
130
  self.qat_trainer.prepare()
131
  print(f"[QuantTool] QAT 准备完成")
132
 
133
+ # 保存 num_epochs 供后续 train() 使用
134
+ self._qat_epochs = num_epochs
135
+
136
  return self.qat_trainer
137
 
138
+ def run_qat_training(self, epochs: Optional[int] = None):
139
+ """运行 QAT 训练"""
140
+ if self.qat_trainer is None:
141
+ raise ValueError("请先调用 prepare_qat()")
142
+
143
+ epochs = epochs or getattr(self, '_qat_epochs', 3)
144
+ # QATTrainer.train() 签名:(epochs, lr, batch_size, max_len)
145
+ self.qat_trainer.train(epochs=epochs)
146
+ self.quantized_model = self.qat_trainer.qat_model
147
+
148
  def evaluate_quantized(
149
  self,
150
  texts: List[str],
 
279
  if format == "safetensors":
280
  try:
281
  import safetensors.torch
282
+ safetensors.torch.save_model(self.quantized_model, path)
 
 
 
 
 
 
 
 
283
  except ImportError:
284
  print(f"[QuantTool] 警告:safetensors 未安装,使用 PyTorch 格式")
285
  format = "pytorch"
 
290
  print(f"[QuantTool] 保存完成")
291
 
292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  def quantize_model(
294
  model: nn.Module,
295
  method: str = "dynamic",
 
314
  return tool.dynamic_quantize(bits=bits)
315
  elif method == "qat":
316
  trainer = tool.prepare_qat(**kwargs)
317
+ epochs = kwargs.get('num_epochs', 3)
318
+ trainer.train(epochs=epochs)
319
  return tool.quantized_model
320
  else:
321
  raise ValueError(f"不支持的量化方法: {method}")
 
332
  print()
333
  print("用法:")
334
  print(" from evaluation.quantization_tool import QuantizationTool")
335
+ print(" tool = QuantizationTool(model, model_path='fusion-mini')")
336
  print(" quantized_model = tool.dynamic_quantize(bits=8)")
337
  print(" metrics = tool.compare_models(texts)")
inference/ollama_deploy_v2.py CHANGED
@@ -579,12 +579,20 @@ def _fallback_export_gguf(model_path: str, output_path: str) -> Optional[str]:
579
  merged_state.update(shard_state)
580
  st.save_file(merged_state, export_path)
581
  else:
582
- # Single-file model
583
  weight_files = list(model_path_obj.glob("*.safetensors")) + list(model_path_obj.glob("*.bin"))
584
  if not weight_files:
585
  logger.error("No model weight files found")
586
  return None
587
- st.save_file(model.state_dict(), export_path)
 
 
 
 
 
 
 
 
588
  logger.info(f"Exported model weights to: {export_path}")
589
  logger.info("NOTE: This is a safetensors export, not GGUF. For Ollama deployment,")
590
  logger.info(" convert this to GGUF using llama.cpp after ensuring architecture compatibility.")
 
579
  merged_state.update(shard_state)
580
  st.save_file(merged_state, export_path)
581
  else:
582
+ # Single-file model: load actual weights, don't save random init
583
  weight_files = list(model_path_obj.glob("*.safetensors")) + list(model_path_obj.glob("*.bin"))
584
  if not weight_files:
585
  logger.error("No model weight files found")
586
  return None
587
+ # Load the actual weights
588
+ import safetensors.torch as st
589
+ import torch
590
+ weight_file = weight_files[0]
591
+ if weight_file.suffix == '.safetensors':
592
+ state_dict = st.load_file(str(weight_file))
593
+ else: # .bin (PyTorch)
594
+ state_dict = torch.load(str(weight_file), map_location='cpu')
595
+ st.save_file(state_dict, export_path)
596
  logger.info(f"Exported model weights to: {export_path}")
597
  logger.info("NOTE: This is a safetensors export, not GGUF. For Ollama deployment,")
598
  logger.info(" convert this to GGUF using llama.cpp after ensuring architecture compatibility.")
models/optimized_sbla_attention.py DELETED
@@ -1,266 +0,0 @@
1
- """
2
- 优化的 SBLA 注意力 - 尝试进一步优化速度(虽然已经很快了)
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
-
10
- sys.path.insert(0, '.')
11
-
12
-
13
- class OptimizedSBLAttention(torch.nn.Module):
14
- """
15
- 优化的 SBLA 注意力模块(尝试进一步优化速度)
16
-
17
- 优化策略:
18
- 1. 使用混合精度(FP16)
19
- 2. 减少不必要的计算
20
- 3. 优化内存访问模式
21
- """
22
-
23
- def __init__(self, config):
24
- super().__init__()
25
-
26
- self.hidden_size = config.hidden_size
27
- self.num_heads = config.num_attention_heads
28
- self.head_dim = self.hidden_size // self.num_heads
29
- self.window_size = getattr(config, 'sbla_window_size', 512)
30
- self.num_key_value_heads = getattr(config, 'num_key_value_heads', self.num_heads)
31
- self.num_key_value_groups = self.num_heads // self.num_key_value_heads
32
-
33
- # 投影层
34
- self.q_proj = torch.nn.Linear(self.hidden_size, self.hidden_size, bias=False)
35
- self.k_proj = torch.nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
36
- self.v_proj = torch.nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
37
- self.o_proj = torch.nn.Linear(self.hidden_size, self.hidden_size, bias=False)
38
-
39
- # SBLA 门控(可选)
40
- self.use_sbla = True
41
- self.sbla_gate = torch.nn.Linear(self.hidden_size, self.hidden_size, bias=False)
42
-
43
- # 混合精度(可选)
44
- self.use_fp16 = False # 默认关闭,因为已经很快了
45
-
46
- # Dropout
47
- self.dropout = torch.nn.Dropout(getattr(config, 'attention_probs_dropout_prob', 0.1))
48
-
49
- def _repeat_kv(self, hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
50
- """重复 KV heads 以匹配 Q heads"""
51
- if n_rep == 1:
52
- return hidden_states
53
-
54
- batch, seq_len, num_key_value_heads, head_dim = hidden_states.shape
55
- hidden_states = hidden_states[:, :, :, None, :].expand(
56
- batch, seq_len, num_key_value_heads, n_rep, head_dim
57
- )
58
- hidden_states = hidden_states.reshape(batch, seq_len, num_key_value_heads * n_rep, head_dim)
59
- return hidden_states
60
-
61
- def forward(
62
- self,
63
- hidden_states: torch.Tensor,
64
- attention_mask: Optional[torch.Tensor] = None,
65
- past_key_value: Optional[Tuple[torch.Tensor]] = None,
66
- use_cache: bool = False,
67
- ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor]]]:
68
- """
69
- 优化的前向传播
70
- """
71
- batch_size, seq_len, hidden_size = hidden_states.shape
72
-
73
- # 混合精度(可选)
74
- if self.use_fp16 and hidden_states.device.type == 'cuda':
75
- with torch.cuda.amp.autocast():
76
- return self._forward_impl(
77
- hidden_states, attention_mask, past_key_value, use_cache
78
- )
79
- else:
80
- return self._forward_impl(
81
- hidden_states, attention_mask, past_key_value, use_cache
82
- )
83
-
84
- def _forward_impl(
85
- self,
86
- hidden_states: torch.Tensor,
87
- attention_mask: Optional[torch.Tensor] = None,
88
- past_key_value: Optional[Tuple[torch.Tensor]] = None,
89
- use_cache: bool = False,
90
- ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor]]]:
91
- """实际的前向传播实现"""
92
- batch_size, seq_len, hidden_size = hidden_states.shape
93
-
94
- # 1. 线性投影
95
- query_states = self.q_proj(hidden_states)
96
- key_states = self.k_proj(hidden_states)
97
- value_states = self.v_proj(hidden_states)
98
-
99
- # 2. 形状重塑
100
- query_states = query_states.view(batch_size, seq_len, self.num_heads, self.head_dim)
101
- key_states = key_states.view(batch_size, seq_len, self.num_key_value_heads, self.head_dim)
102
- value_states = value_states.view(batch_size, seq_len, self.num_key_value_heads, self.head_dim)
103
-
104
- # 3. KV 缓存(可选)
105
- if past_key_value is not None:
106
- key_states = torch.cat([past_key_value[0], key_states], dim=1)
107
- value_states = torch.cat([past_key_value[1], value_states], dim=1)
108
-
109
- past_key_value = (key_states, value_states) if use_cache else None
110
-
111
- # 4. 重复 KV heads(如果 GQA 启用)
112
- key_states = self._repeat_kv(key_states, self.num_key_value_groups)
113
- value_states = self._repeat_kv(value_states, self.num_key_value_groups)
114
-
115
- # 5. 转置为 (batch, num_heads, seq_len, head_dim)
116
- query_states = query_states.transpose(1, 2)
117
- key_states = key_states.transpose(1, 2)
118
- value_states = value_states.transpose(1, 2)
119
-
120
- # 6. 注意力分数计算
121
- attn_weights = torch.matmul(query_states, key_states.transpose(-1, -2)) / (self.head_dim ** 0.5)
122
-
123
- # 7. 注意力掩码(优化:避免不必要的形状操作)
124
- if attention_mask is not None:
125
- # 确保 attention_mask 形状正确
126
- if attention_mask.dim() == 2:
127
- # (batch, seq_len) -> (batch, 1, 1, seq_len)
128
- attention_mask = attention_mask[:, None, None, :]
129
- elif attention_mask.dim() == 3:
130
- # (batch, 1, seq_len, seq_len) -> (batch, 1, seq_len, seq_len)
131
- pass
132
-
133
- # 应用掩码
134
- attn_weights = attn_weights + attention_mask
135
-
136
- # 8. Softmax
137
- attn_weights = torch.softmax(attn_weights, dim=-1)
138
- attn_weights = self.dropout(attn_weights)
139
-
140
- # 9. 注意力输出
141
- attn_output = torch.matmul(attn_weights, value_states)
142
-
143
- # 10. 转置回 (batch, seq_len, num_heads, head_dim)
144
- attn_output = attn_output.transpose(1, 2).contiguous()
145
- attn_output = attn_output.view(batch_size, seq_len, hidden_size)
146
-
147
- # 11. 输出投影
148
- attn_output = self.o_proj(attn_output)
149
-
150
- # 12. SBLA 门控(可选,优化:避免不必要的计算)
151
- if self.use_sbla:
152
- # 简化的 SBLA:只应用门控,不扩展潜向量
153
- gate = torch.sigmoid(self.sbla_gate(hidden_states))
154
- attn_output = attn_output * gate
155
-
156
- return attn_output, past_key_value
157
-
158
- @torch.no_grad()
159
- def benchmark(self, seq_len=32, num_runs=100):
160
- """
161
- 性能基准测试
162
- """
163
- print(f"[BENCHMARK] 优化版 SBLA 注意力性能分析(seq_len={seq_len}, num_runs={num_runs})...")
164
-
165
- self.eval()
166
-
167
- # 创建测试输入
168
- hidden_states = torch.randn(1, seq_len, self.hidden_size)
169
- attention_mask = torch.ones(1, seq_len)
170
-
171
- # 预热
172
- for _ in range(10):
173
- self.forward(hidden_states, attention_mask)
174
-
175
- # 计时
176
- torch.cuda.synchronize() if hidden_states.device.type == 'cuda' else None
177
- start = torch.cuda.Event(enable_timing=True) if hidden_states.device.type == 'cuda' else None
178
- end = torch.cuda.Event(enable_timing=True) if hidden_states.device.type == 'cuda' else None
179
-
180
- times = []
181
- for i in range(num_runs):
182
- if hidden_states.device.type == 'cuda':
183
- start.record()
184
- self.forward(hidden_states, attention_mask)
185
- end.record()
186
- torch.cuda.synchronize()
187
- times.append(start.elapsed_time(end))
188
- else:
189
- import time
190
- t0 = time.time()
191
- self.forward(hidden_states, attention_mask)
192
- t1 = time.time()
193
- times.append((t1 - t0) * 1000) # 转换为 ms
194
-
195
- # 统计
196
- avg_time = sum(times) / len(times)
197
- min_time = min(times)
198
- max_time = max(times)
199
-
200
- print(f" 平均时间: {avg_time:.2f} ms")
201
- print(f" 最短时间: {min_time:.2f} ms")
202
- print(f" 最长时间: {max_time:.2f} ms")
203
-
204
- return avg_time, min_time, max_time
205
-
206
-
207
- if __name__ == "__main__":
208
- print("=" * 60)
209
- print("Fusion-LLM 优化的 SBLA 注意力测试")
210
- print("=" * 60)
211
- print()
212
-
213
- # 创建测试配置
214
- class TestConfig:
215
- def __init__(self):
216
- self.hidden_size = 64
217
- self.num_attention_heads = 2
218
- self.sbla_window_size = 512
219
- self.num_key_value_heads = 2
220
- self.attention_probs_dropout_prob = 0.1
221
-
222
- config = TestConfig()
223
-
224
- # 创建优化版 SBLA 注意力
225
- print("[1] 创建优化版 SBLA 注意力...")
226
- attn = OptimizedSBLAttention(config)
227
- print(f" 配置: hidden_size={config.hidden_size}, num_heads={config.num_attention_heads}")
228
- print()
229
-
230
- # 测试前向传播
231
- print("[2] 测试前向传播...")
232
- hidden_states = torch.randn(1, 8, config.hidden_size)
233
- attention_mask = torch.ones(1, 8)
234
-
235
- output, cache = attn(hidden_states, attention_mask)
236
- print(f" 输入形状: {hidden_states.shape}")
237
- print(f" 输出形状: {output.shape}")
238
- print()
239
-
240
- # 性能基准测试
241
- print("[3] 性能基准测试...")
242
- avg_time, min_time, max_time = attn.benchmark(seq_len=32, num_runs=100)
243
- print()
244
-
245
- # 与原版比较(如果可用)
246
- print("[4] 与原版比较...")
247
- try:
248
- from models.sbla_attention import SBLAttention
249
-
250
- # 创建原版
251
- original_attn = SBLAttention(config)
252
-
253
- # 基准测试
254
- original_attn.benchmark(seq_len=32, num_runs=100)
255
- print()
256
-
257
- print("[INFO] 优化版 vs 原版:")
258
- print(f" 优化版平均时间: {avg_time:.2f} ms")
259
- print(f" 原版平均时间: (见上方)")
260
- print()
261
- except:
262
- print(" [WARN] 原版不可用,跳过比较")
263
- print()
264
-
265
- print("[PASS] 优化的 SBLA 注意力测试通过")
266
- sys.exit(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/sbla_attention.py CHANGED
@@ -92,10 +92,11 @@ class SBLAttention(nn.Module):
92
  assert mode in ("pure_sbla", "hybrid"), \
93
  f"mode 必须是 'pure_sbla' 或 'hybrid',得到 '{mode}'"
94
 
95
- # Q/K/V 投影 (GQA: K/V use fewer heads)
96
- self.q_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
97
- self.k_proj = nn.Linear(hidden_size, self.num_key_value_heads * self.kv_head_dim, bias=False)
98
- self.v_proj = nn.Linear(hidden_size, self.num_key_value_heads * self.kv_head_dim, bias=False)
 
99
 
100
  # 潜向量投影(跨块关联)
101
  self.latent_q_proj = nn.Linear(hidden_size, latent_dim, bias=False)
 
92
  assert mode in ("pure_sbla", "hybrid"), \
93
  f"mode 必须是 'pure_sbla' 或 'hybrid',得到 '{mode}'"
94
 
95
+ # S-NEW-8 FIX: Remove unused Q/K/V projections (waste ~1.6B params for 32 layers)
96
+ # FusionAttention handles projections and RoPE, then calls forward_with_qkv
97
+ # self.q_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
98
+ # self.k_proj = nn.Linear(hidden_size, self.num_key_value_heads * self.kv_head_dim, bias=False)
99
+ # self.v_proj = nn.Linear(hidden_size, self.num_key_value_heads * self.kv_head_dim, bias=False)
100
 
101
  # 潜向量投影(跨块关联)
102
  self.latent_q_proj = nn.Linear(hidden_size, latent_dim, bias=False)
train/dpo_finetune.py CHANGED
@@ -79,6 +79,9 @@ class DPOTrainer:
79
  self.config = config
80
  self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
81
  self.step = 0
 
 
 
82
 
83
  def _get_tokenizer(self) -> object:
84
  """Get tokenizer with fallback to character-level encoding."""
 
79
  self.config = config
80
  self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
81
  self.step = 0
82
+ self._tokenizer = None # M-NEW-11 FIX: Initialize to prevent AttributeError
83
+ self.model = None
84
+ self.ref_model = None
85
 
86
  def _get_tokenizer(self) -> object:
87
  """Get tokenizer with fallback to character-level encoding."""
train/lora_finetune.py CHANGED
@@ -28,7 +28,7 @@ from transformers import (
28
  AutoTokenizer,
29
  TrainingArguments,
30
  Trainer,
31
- DataCollatorForSeq2Seq,
32
  GenerationConfig,
33
  )
34
  from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
@@ -204,26 +204,61 @@ def create_local_model(
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
@@ -348,11 +383,7 @@ def train(args):
348
  model=model,
349
  args=training_args,
350
  train_dataset=train_dataset,
351
- data_collator=DataCollatorForSeq2Seq(
352
- tokenizer,
353
- model=model,
354
- padding="longest",
355
- ),
356
  )
357
 
358
  # 7. 开始训练
 
28
  AutoTokenizer,
29
  TrainingArguments,
30
  Trainer,
31
+ DataCollatorForLanguageModeling,
32
  GenerationConfig,
33
  )
34
  from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
 
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
+ # S-NEW-9 FIX: QLoRA requires proper bitsandbytes integration
208
+ # For local models created from scratch, we can't use HF's AutoModel quantization.
209
+ # Instead, we quantize the model directly with bitsandbytes if available.
 
210
  if quantize:
211
  if load_in_4bit:
212
  logger.info("[create_local_model] Using 4-bit quantization (QLoRA)")
213
  try:
214
+ import bitsandbytes as bnb
215
+ # Replace Linear layers with 4-bit quantized versions
216
+ for name, module in model.named_modules():
217
+ if isinstance(module, nn.Linear) and not any(x in name for x in ['lora', 'head', 'embed']):
218
+ # Create 4-bit quantized linear (using bitsandbytes nf4)
219
+ quantized = bnb.nn.Linear4bit(
220
+ module.in_features,
221
+ module.out_features,
222
+ bias=module.bias is not None,
223
+ quant_type='nf4',
224
+ compute_dtype=torch.float16
225
+ )
226
+ # Replace in model
227
+ parent_name = '.'.join(name.split('.')[:-1])
228
+ child_name = name.split('.')[-1]
229
+ if parent_name:
230
+ parent = dict(model.named_modules())[parent_name]
231
+ setattr(parent, child_name, quantized)
232
+ else:
233
+ setattr(model, child_name, quantized)
234
+ logger.info("[create_local_model] 4-bit quantization applied (nf4)")
235
  except ImportError:
236
+ logger.warning("bitsandbytes not installed, 4-bit quantization DISABLED")
237
+ logger.warning("Model will train in FP32 - install bitsandbytes for true QLoRA")
238
  model = prepare_model_for_kbit_training(model)
239
  elif load_in_8bit:
240
  logger.info("[create_local_model] Using 8-bit quantization")
241
+ try:
242
+ import bitsandbytes as bnb
243
+ # Replace Linear layers with 8-bit quantized versions
244
+ for name, module in model.named_modules():
245
+ if isinstance(module, nn.Linear) and not any(x in name for x in ['lora', 'head', 'embed']):
246
+ quantized = bnb.nn.Linear8bitLt(
247
+ module.in_features,
248
+ module.out_features,
249
+ bias=module.bias is not None,
250
+ has_fp16_weights=False
251
+ )
252
+ parent_name = '.'.join(name.split('.')[:-1])
253
+ child_name = name.split('.')[-1]
254
+ if parent_name:
255
+ parent = dict(model.named_modules())[parent_name]
256
+ setattr(parent, child_name, quantized)
257
+ else:
258
+ setattr(model, child_name, quantized)
259
+ logger.info("[create_local_model] 8-bit quantization applied")
260
+ except ImportError:
261
+ logger.warning("bitsandbytes not installed, 8-bit quantization DISABLED")
262
  model = prepare_model_for_kbit_training(model)
263
 
264
  return model, config
 
383
  model=model,
384
  args=training_args,
385
  train_dataset=train_dataset,
386
+ data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
 
 
 
 
387
  )
388
 
389
  # 7. 开始训练