Spaces:
Running
Running
| """ | |
| 模型量化工具 | |
| 完整的模型量化流程: | |
| 1. 动态量化(DyQuant) | |
| 2. 量化感知训练(QAT) | |
| 3. 量化后评估 | |
| 4. 性能对比(量化 vs 原始) | |
| """ | |
| import sys | |
| import time | |
| from typing import Dict, List, Optional, Tuple | |
| import torch | |
| import torch.nn as nn | |
| from torch.quantization import get_default_qconfig, prepare_qat, convert | |
| sys.path.insert(0, '.') | |
| from evaluation.metrics import ModelEvaluator, EvaluationMetrics | |
| from inference.dyquant import DyQuantConverter, QATTrainer, QuantConfig | |
| class QuantizationTool: | |
| """模型量化工具(完整流程)""" | |
| def __init__( | |
| self, | |
| model: nn.Module, | |
| tokenizer = None, | |
| device: str = "cpu", | |
| model_path: Optional[str] = None, | |
| ): | |
| """ | |
| 初始化量化工具 | |
| 参数: | |
| model: 要量化的模型 | |
| tokenizer: tokenizer(可选) | |
| device: 设备 | |
| model_path: 模型路径(用于 DyQuant) | |
| """ | |
| self.model = model | |
| self.tokenizer = tokenizer | |
| self.device = device | |
| self.model_path = model_path or "fusion-mini" | |
| self.original_model = None | |
| self.quantized_model = None | |
| self.qat_trainer = None | |
| self.converter = None | |
| def backup_original_model(self): | |
| """备份原始模型""" | |
| print("[QuantTool] 备份原始模型...") | |
| import copy | |
| self.original_model = copy.deepcopy(self.model) | |
| print("[QuantTool] 备份完成") | |
| def dynamic_quantize( | |
| self, | |
| bits: int = 8, | |
| mode: str = "symmetric", | |
| ) -> nn.Module: | |
| """ | |
| 动态量化(post-training quantization) | |
| 参数: | |
| bits: 量化位数(4/8) | |
| mode: 量化模式(symmetric/asymmetric) | |
| 返回: | |
| 量化后的模型 | |
| """ | |
| print(f"[QuantTool] 开始动态量化({bits}-bit, {mode})...") | |
| # 使用正确的 QuantConfig API | |
| config = QuantConfig( | |
| model_path=self.model_path, | |
| bits=bits, | |
| mixed_precision=False, | |
| ) | |
| self.converter = DyQuantConverter(config) | |
| self.converter.model = self.model # 注入已加载的模型 | |
| self.quantized_model = self.converter.convert() | |
| print(f"[QuantTool] 动态量化完成") | |
| return self.quantized_model | |
| def prepare_qat( | |
| self, | |
| learning_rate: float = 1e-4, | |
| num_epochs: int = 3, | |
| train_data: Optional[str] = None, | |
| ) -> "QATTrainer": | |
| """ | |
| 准备量化感知训练(QAT) | |
| 参数: | |
| learning_rate: 学习率 | |
| num_epochs: 训练轮数(注意:QATTrainer 没有 num_epochs 参数) | |
| train_data: 训练数据路径 | |
| 返回: | |
| QATTrainer 对象 | |
| """ | |
| print(f"[QuantTool] 准备量化感知训练(QAT)...") | |
| print(f" 学习率: {learning_rate}") | |
| print(f" 训练轮数: {num_epochs}") | |
| # 使用正确的 QuantConfig API | |
| config = QuantConfig( | |
| model_path=self.model_path, | |
| bits=4, | |
| mixed_precision=True, | |
| ) | |
| # QATTrainer 签名:(config, train_data, learning_rate, warmup_steps) | |
| self.qat_trainer = QATTrainer( | |
| config=config, | |
| train_data=train_data, | |
| learning_rate=learning_rate, | |
| warmup_steps=100, | |
| ) | |
| # 注入已加载的模型,避免重复加载 | |
| self.qat_trainer.model = self.model | |
| self.qat_trainer.prepare() | |
| print(f"[QuantTool] QAT 准备完成") | |
| # 保存 num_epochs 供后续 train() 使用 | |
| self._qat_epochs = num_epochs | |
| return self.qat_trainer | |
| def run_qat_training(self, epochs: Optional[int] = None): | |
| """运行 QAT 训练""" | |
| if self.qat_trainer is None: | |
| raise ValueError("请先调用 prepare_qat()") | |
| epochs = epochs or getattr(self, '_qat_epochs', 3) | |
| # QATTrainer.train() 签名:(epochs, lr, batch_size, max_len) | |
| self.qat_trainer.train(epochs=epochs) | |
| self.quantized_model = self.qat_trainer.qat_model | |
| def evaluate_quantized( | |
| self, | |
| texts: List[str], | |
| max_length: int = 512, | |
| ) -> EvaluationMetrics: | |
| """ | |
| 评估量化后的模型 | |
| 参数: | |
| texts: 评估文本 | |
| max_length: 最大长度 | |
| 返回: | |
| 评估指标 | |
| """ | |
| if self.quantized_model is None: | |
| raise ValueError("请先量化模型") | |
| print(f"[QuantTool] 评估量化后的模型...") | |
| evaluator = ModelEvaluator( | |
| model=self.quantized_model, | |
| tokenizer=self.tokenizer, | |
| device=self.device, | |
| ) | |
| metrics = evaluator.evaluate(texts, max_length) | |
| print(f"[QuantTool] 评估完成") | |
| print(metrics) | |
| return metrics | |
| def compare_models( | |
| self, | |
| texts: List[str], | |
| max_length: int = 512, | |
| ) -> Dict[str, EvaluationMetrics]: | |
| """ | |
| 对比原始模型和量化模型的性能 | |
| 参数: | |
| texts: 评估文本 | |
| max_length: 最大长度 | |
| 返回: | |
| 包含原始模型和量化模型评估指标的字典 | |
| """ | |
| if self.original_model is None: | |
| self.backup_original_model() | |
| if self.quantized_model is None: | |
| raise ValueError("请先量化模型") | |
| print(f"[QuantTool] 对比原始模型和量化模型...") | |
| print() | |
| # 1. 评估原始模型 | |
| print("[1] 评估原始模型...") | |
| original_evaluator = ModelEvaluator( | |
| model=self.original_model, | |
| tokenizer=self.tokenizer, | |
| device=self.device, | |
| ) | |
| original_metrics = original_evaluator.evaluate(texts, max_length) | |
| print(f" 原始模型 Perplexity: {original_metrics.perplexity:.4f}") | |
| print(f" 原始模型 Loss: {original_metrics.loss:.4f}") | |
| print() | |
| # 2. 评估量化模型 | |
| print("[2] 评估量化模型...") | |
| quantized_evaluator = ModelEvaluator( | |
| model=self.quantized_model, | |
| tokenizer=self.tokenizer, | |
| device=self.device, | |
| ) | |
| quantized_metrics = quantized_evaluator.evaluate(texts, max_length) | |
| print(f" 量化模型 Perplexity: {quantized_metrics.perplexity:.4f}") | |
| print(f" 量化模型 Loss: {quantized_metrics.loss:.4f}") | |
| print() | |
| # 3. 计算差异 | |
| print("[3] 性能差异...") | |
| perplexity_diff = quantized_metrics.perplexity - original_metrics.perplexity | |
| loss_diff = quantized_metrics.loss - original_metrics.loss | |
| print(f" Perplexity 差异: {perplexity_diff:+.4f}") | |
| print(f" Loss 差异: {loss_diff:+.4f}") | |
| print() | |
| # 4. 模型大小对比 | |
| original_size = self._get_model_size(self.original_model) | |
| quantized_size = self._get_model_size(self.quantized_model) | |
| size_reduction = (1 - quantized_size / original_size) * 100 | |
| print("[4] 模型大小对比...") | |
| print(f" 原始模型大小: {original_size / 1e6:.2f} MB") | |
| print(f" 量化模型大小: {quantized_size / 1e6:.2f} MB") | |
| print(f" 大小减少: {size_reduction:.2f}%") | |
| print() | |
| print("[QuantTool] 对比完成") | |
| return { | |
| "original": original_metrics, | |
| "quantized": quantized_metrics, | |
| } | |
| def _get_model_size(self, model: nn.Module) -> int: | |
| """获取模型大小(字节)""" | |
| total_size = 0 | |
| for param in model.parameters(): | |
| total_size += param.nelement() * param.element_size() | |
| return total_size | |
| def save_quantized( | |
| self, | |
| path: str, | |
| format: str = "safetensors", | |
| ): | |
| """ | |
| 保存量化后的模型 | |
| 参数: | |
| path: 保存路径 | |
| format: 格式(safetensors/pytorch) | |
| """ | |
| if self.quantized_model is None: | |
| raise ValueError("请先量化模型") | |
| print(f"[QuantTool] 保存量化模型到 {path}...") | |
| if format == "safetensors": | |
| try: | |
| import safetensors.torch | |
| safetensors.torch.save_file(self.quantized_model.state_dict(), path) | |
| except ImportError: | |
| print(f"[QuantTool] 警告:safetensors 未安装,使用 PyTorch 格式") | |
| format = "pytorch" | |
| if format == "pytorch": | |
| torch.save(self.quantized_model.state_dict(), path) | |
| print(f"[QuantTool] 保存完成") | |
| def quantize_model( | |
| model: nn.Module, | |
| method: str = "dynamic", | |
| bits: int = 8, | |
| **kwargs, | |
| ) -> nn.Module: | |
| """ | |
| 便捷函数:量化模型 | |
| 参数: | |
| model: 要量化的模型 | |
| method: 量化方法(dynamic/qat) | |
| bits: 量化位数 | |
| **kwargs: 其他参数 | |
| 返回: | |
| 量化后的模型 | |
| """ | |
| tool = QuantizationTool(model, **kwargs) | |
| if method == "dynamic": | |
| return tool.dynamic_quantize(bits=bits) | |
| elif method == "qat": | |
| trainer = tool.prepare_qat(**kwargs) | |
| epochs = kwargs.get('num_epochs', 3) | |
| trainer.train(epochs=epochs) | |
| return tool.quantized_model | |
| else: | |
| raise ValueError(f"不支持的量化方法: {method}") | |
| if __name__ == "__main__": | |
| print("[QuantTool] 模型量化工具") | |
| print() | |
| print("功能:") | |
| print(" 1. 动态量化(post-training)") | |
| print(" 2. 量化感知训练(QAT)") | |
| print(" 3. 量化后评估") | |
| print(" 4. 性能对比(量化 vs 原始)") | |
| print() | |
| print("用法:") | |
| print(" from evaluation.quantization_tool import QuantizationTool") | |
| print(" tool = QuantizationTool(model, model_path='fusion-mini')") | |
| print(" quantized_model = tool.dynamic_quantize(bits=8)") | |
| print(" metrics = tool.compare_models(texts)") | |