Spaces:
Running
Running
zhan1206 commited on
Commit ·
86985bf
1
Parent(s): a959e9a
fix: v13 external audit - CRITICAL/HIGH/MEDIUM fixes
Browse filesCRITICAL (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 +50 -42
- inference/ollama_deploy_v2.py +10 -2
- models/optimized_sbla_attention.py +0 -266
- models/sbla_attention.py +5 -4
- train/dpo_finetune.py +3 -0
- train/lora_finetune.py +49 -18
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
|
| 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 |
-
|
| 53 |
-
|
| 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 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
mixed_precision=False,
|
| 81 |
-
),
|
| 82 |
)
|
| 83 |
|
| 84 |
-
self.
|
| 85 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 110 |
learning_rate=learning_rate,
|
| 111 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 96 |
-
|
| 97 |
-
self.
|
| 98 |
-
self.
|
|
|
|
| 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 |
-
|
| 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 |
-
#
|
| 208 |
-
# For
|
| 209 |
-
#
|
| 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 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
except ImportError:
|
| 223 |
-
logger.warning("bitsandbytes not installed, 4-bit quantization
|
|
|
|
| 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=
|
| 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. 开始训练
|