| import torch
|
| import torch.nn as nn
|
| from typing import Optional, Dict, Any
|
| import onnx
|
| import onnxruntime as ort
|
| import os
|
|
|
|
|
| class ModelOptimizer:
|
| """模型优化器"""
|
| def __init__(self, model: nn.Module, device: str = 'cuda'):
|
| self.model = model
|
| self.device = device
|
| self.optimized_model = None
|
|
|
| def optimize_for_inference(self, use_jit: bool = True, use_cuda_graph: bool = False):
|
| """优化模型用于推理"""
|
| self.model.eval()
|
|
|
|
|
| if use_jit:
|
| self._jit_compile()
|
|
|
| if use_cuda_graph and torch.cuda.is_available():
|
| self._capture_cuda_graph()
|
|
|
|
|
| self._apply_inference_optimizations()
|
|
|
| return self.optimized_model or self.model
|
|
|
| def _jit_compile(self):
|
| """使用TorchScript编译模型"""
|
| try:
|
|
|
| example_input = torch.randn(1, 4, 64, 64, device=self.device)
|
| example_timestep = torch.tensor([500], device=self.device)
|
| example_context = torch.randn(1, 77, 768, device=self.device)
|
|
|
|
|
| scripted_model = torch.jit.trace(
|
| self.model,
|
| (example_input, example_timestep, example_context),
|
| check_trace=False
|
| )
|
|
|
| self.optimized_model = scripted_model
|
| print("模型已使用TorchScript编译")
|
| except Exception as e:
|
| print(f"TorchScript编译失败: {e}")
|
|
|
| def _capture_cuda_graph(self):
|
| """捕获CUDA图(用于重复推理)"""
|
| if not torch.cuda.is_available():
|
| return
|
|
|
|
|
| static_input = torch.randn(1, 4, 64, 64, device='cuda', dtype=torch.float16)
|
| static_timestep = torch.tensor([500], device='cuda')
|
| static_context = torch.randn(1, 77, 768, device='cuda', dtype=torch.float16)
|
|
|
|
|
| with torch.no_grad():
|
| for _ in range(3):
|
| _ = self.model(static_input, static_timestep, static_context)
|
|
|
|
|
| graph = torch.cuda.CUDAGraph()
|
| with torch.cuda.graph(graph):
|
| static_output = self.model(static_input, static_timestep, static_context)
|
|
|
|
|
| def graph_executor(input_tensor, timestep, context):
|
| static_input.copy_(input_tensor)
|
| static_timestep.copy_(timestep)
|
| static_context.copy_(context)
|
| graph.replay()
|
| return static_output.clone()
|
|
|
| self.optimized_model = graph_executor
|
| print("已捕获CUDA图")
|
|
|
| def _apply_inference_optimizations(self):
|
| """应用推理优化"""
|
|
|
| self.model.eval()
|
|
|
|
|
| if hasattr(torch, 'compile'):
|
| try:
|
| self.model = torch.compile(self.model, mode='max-autotune')
|
| print("模型已使用torch.compile优化")
|
| except:
|
| pass
|
|
|
|
|
| if self.device == 'cuda':
|
| self.model.half()
|
| print("模型已转换为半精度")
|
|
|
| def quantize(self, quantization_mode: str = 'dynamic'):
|
| """量化模型"""
|
| if quantization_mode == 'dynamic':
|
|
|
| quantized_model = torch.quantization.quantize_dynamic(
|
| self.model,
|
| {nn.Linear, nn.Conv2d},
|
| dtype=torch.qint8
|
| )
|
| self.optimized_model = quantized_model
|
| print("模型已动态量化")
|
|
|
| elif quantization_mode == 'static':
|
|
|
| print("静态量化需要校准数据,暂未实现")
|
|
|
| else:
|
| raise ValueError(f"未知的量化模式: {quantization_mode}")
|
|
|
| def prune(self, pruning_rate: float = 0.2):
|
| """剪枝模型"""
|
| from torch.nn.utils import prune
|
|
|
|
|
| for name, module in self.model.named_modules():
|
| if isinstance(module, (nn.Linear, nn.Conv2d)):
|
| prune.l1_unstructured(module, name='weight', amount=pruning_rate)
|
| prune.remove(module, 'weight')
|
|
|
| print(f"模型已剪枝,剪枝率: {pruning_rate}")
|
|
|
| def get_model_size(self) -> Dict[str, float]:
|
| """获取模型大小"""
|
|
|
| total_params = sum(p.numel() for p in self.model.parameters())
|
| trainable_params = sum(p.numel() for p in self.model.parameters() if p.requires_grad)
|
|
|
|
|
| param_size = 0
|
| for param in self.model.parameters():
|
| param_size += param.nelement() * param.element_size()
|
|
|
| buffer_size = 0
|
| for buffer in self.model.buffers():
|
| buffer_size += buffer.nelement() * buffer.element_size()
|
|
|
| size_mb = (param_size + buffer_size) / 1024**2
|
|
|
| return {
|
| 'total_params': total_params,
|
| 'trainable_params': trainable_params,
|
| 'size_mb': size_mb
|
| }
|
|
|
|
|
| class ONNXExporter:
|
| """ONNX导出器"""
|
| def __init__(self, model: nn.Module):
|
| self.model = model
|
|
|
| def export(
|
| self,
|
| output_path: str,
|
| input_shape: tuple = (1, 4, 64, 64),
|
| opset_version: int = 14,
|
| dynamic_axes: Optional[Dict] = None
|
| ):
|
| """导出为ONNX格式"""
|
|
|
| self.model.eval()
|
|
|
|
|
| dummy_input = torch.randn(*input_shape)
|
| dummy_timestep = torch.tensor([500])
|
| dummy_context = torch.randn(1, 77, 768)
|
|
|
|
|
| if dynamic_axes is None:
|
| dynamic_axes = {
|
| 'input': {0: 'batch_size'},
|
| 'timestep': {0: 'batch_size'},
|
| 'context': {0: 'batch_size'},
|
| 'output': {0: 'batch_size'}
|
| }
|
|
|
|
|
| torch.onnx.export(
|
| self.model,
|
| (dummy_input, dummy_timestep, dummy_context),
|
| output_path,
|
| input_names=['input', 'timestep', 'context'],
|
| output_names=['output'],
|
| dynamic_axes=dynamic_axes,
|
| opset_version=opset_version,
|
| do_constant_folding=True,
|
| verbose=False
|
| )
|
|
|
| print(f"模型已导出为ONNX: {output_path}")
|
|
|
|
|
| self._validate_onnx(output_path)
|
|
|
| def _validate_onnx(self, onnx_path: str):
|
| """验证ONNX模型"""
|
| try:
|
| onnx_model = onnx.load(onnx_path)
|
| onnx.checker.check_model(onnx_model)
|
| print("ONNX模型验证成功")
|
| except Exception as e:
|
| print(f"ONNX模型验证失败: {e}")
|
|
|
| def optimize_onnx(self, onnx_path: str, optimized_path: str):
|
| """优化ONNX模型"""
|
| try:
|
|
|
| sess_options = ort.SessionOptions()
|
| sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
|
|
|
|
| ort_session = ort.InferenceSession(onnx_path, sess_options)
|
|
|
|
|
| optimized_model = ort_session.get_model()
|
| onnx.save(optimized_model, optimized_path)
|
|
|
| print(f"ONNX模型已优化: {optimized_path}")
|
| except Exception as e:
|
| print(f"ONNX优化失败: {e}")
|
|
|
|
|
| class MemoryEfficientInference:
|
| """内存高效推理"""
|
| def __init__(self, model: nn.Module, chunk_size: int = 32):
|
| self.model = model
|
| self.chunk_size = chunk_size
|
|
|
| def chunked_inference(self, x: torch.Tensor, t: torch.Tensor, context: torch.Tensor) -> torch.Tensor:
|
| """分块推理,减少内存使用"""
|
| B, C, H, W = x.shape
|
| output = torch.zeros_like(x)
|
|
|
|
|
| for i in range(0, H, self.chunk_size):
|
| for j in range(0, W, self.chunk_size):
|
|
|
| chunk = x[:, :, i:i+self.chunk_size, j:j+self.chunk_size]
|
|
|
|
|
| with torch.no_grad():
|
| chunk_output = self.model(chunk, t, context)
|
|
|
|
|
| output[:, :, i:i+self.chunk_size, j:j+self.chunk_size] = chunk_output
|
|
|
| return output
|
|
|
| def tiled_inference(self, x: torch.Tensor, t: torch.Tensor, context: torch.Tensor, tile_size: int = 512) -> torch.Tensor:
|
| """平铺推理,用于大图像"""
|
| B, C, H, W = x.shape
|
|
|
|
|
| if H <= tile_size and W <= tile_size:
|
| with torch.no_grad():
|
| return self.model(x, t, context)
|
|
|
|
|
| n_tiles_h = (H + tile_size - 1) // tile_size
|
| n_tiles_w = (W + tile_size - 1) // tile_size
|
|
|
| output = torch.zeros_like(x)
|
|
|
|
|
| for i in range(n_tiles_h):
|
| for j in range(n_tiles_w):
|
|
|
| h_start = i * tile_size
|
| w_start = j * tile_size
|
| h_end = min(h_start + tile_size, H)
|
| w_end = min(w_start + tile_size, W)
|
|
|
|
|
| tile = x[:, :, h_start:h_end, w_start:w_end]
|
|
|
|
|
| with torch.no_grad():
|
| tile_output = self.model(tile, t, context)
|
|
|
|
|
| output[:, :, h_start:h_end, w_start:w_end] = tile_output
|
|
|
| return output
|
|
|
|
|
| class InferenceBenchmark:
|
| """推理基准测试"""
|
| def __init__(self, model: nn.Module, device: str = 'cuda'):
|
| self.model = model
|
| self.device = device
|
|
|
| def benchmark(
|
| self,
|
| input_shape: tuple = (1, 4, 64, 64),
|
| num_iterations: int = 100,
|
| warmup_iterations: int = 10
|
| ) -> Dict[str, float]:
|
| """运行基准测试"""
|
|
|
| x = torch.randn(*input_shape, device=self.device)
|
| t = torch.tensor([500], device=self.device)
|
| context = torch.randn(1, 77, 768, device=self.device)
|
|
|
|
|
| print("预热...")
|
| with torch.no_grad():
|
| for _ in range(warmup_iterations):
|
| _ = self.model(x, t, context)
|
|
|
|
|
| if torch.cuda.is_available():
|
| torch.cuda.synchronize()
|
|
|
|
|
| print("运行基准测试...")
|
| import time
|
|
|
| times = []
|
|
|
| for i in range(num_iterations):
|
| start_time = time.time()
|
|
|
| with torch.no_grad():
|
| _ = self.model(x, t, context)
|
|
|
| if torch.cuda.is_available():
|
| torch.cuda.synchronize()
|
|
|
| end_time = time.time()
|
| times.append(end_time - start_time)
|
|
|
|
|
| times = torch.tensor(times)
|
|
|
| stats = {
|
| 'mean_ms': times.mean().item() * 1000,
|
| 'std_ms': times.std().item() * 1000,
|
| 'min_ms': times.min().item() * 1000,
|
| 'max_ms': times.max().item() * 1000,
|
| 'fps': 1 / times.mean().item(),
|
| 'num_iterations': num_iterations
|
| }
|
|
|
|
|
| print("\n" + "="*50)
|
| print("推理基准测试结果:")
|
| print(f"平均推理时间: {stats['mean_ms']:.2f} ms")
|
| print(f"标准差: {stats['std_ms']:.2f} ms")
|
| print(f"最小推理时间: {stats['min_ms']:.2f} ms")
|
| print(f"最大推理时间: {stats['max_ms']:.2f} ms")
|
| print(f"FPS: {stats['fps']:.2f}")
|
| print("="*50)
|
|
|
| return stats
|
|
|
|
|
| def optimize_model_for_p4(model: nn.Module) -> nn.Module:
|
| """为P4优化模型"""
|
| optimizer = ModelOptimizer(model)
|
|
|
|
|
| size_info = optimizer.get_model_size()
|
| print(f"优化前模型大小: {size_info['size_mb']:.2f} MB")
|
|
|
|
|
| optimized_model = optimizer.optimize_for_inference(
|
| use_jit=True,
|
| use_cuda_graph=False
|
| )
|
|
|
|
|
| if size_info['size_mb'] > 500:
|
| optimizer.quantize('dynamic')
|
|
|
|
|
| size_info_after = optimizer.get_model_size()
|
| print(f"优化后模型大小: {size_info_after['size_mb']:.2f} MB")
|
| print(f"压缩比: {size_info['size_mb'] / size_info_after['size_mb']:.2f}x")
|
|
|
| return optimized_model
|
|
|
|
|
| def test_optimization():
|
| """测试优化"""
|
| import torch.nn as nn
|
|
|
|
|
| class MockModel(nn.Module):
|
| def __init__(self):
|
| super().__init__()
|
| self.conv1 = nn.Conv2d(4, 64, 3, padding=1)
|
| self.conv2 = nn.Conv2d(64, 4, 3, padding=1)
|
|
|
| def forward(self, x, t, context):
|
| x = self.conv1(x)
|
| x = nn.functional.relu(x)
|
| x = self.conv2(x)
|
| return x
|
|
|
| model = MockModel()
|
|
|
|
|
| optimizer = ModelOptimizer(model)
|
| optimized_model = optimizer.optimize_for_inference()
|
|
|
|
|
| benchmark = InferenceBenchmark(model)
|
| stats = benchmark.benchmark(num_iterations=10)
|
|
|
|
|
| exporter = ONNXExporter(model)
|
| exporter.export('./test_model.onnx', input_shape=(1, 4, 64, 64))
|
|
|
| return optimized_model, stats
|
|
|
|
|
| if __name__ == '__main__':
|
| optimized_model, stats = test_optimization() |