2. PyTorch动态量化实现
任务目标
本案例将训练一个基于LSTM的语言模型(字符级),随后对其应用PyTorch动态量化(torch.quantization.quantize_dynamic),对比量化前后模型的推理速度与内存占用差异。动态量化仅将权重从FP32转为INT8,激活仍以FP32计算,特别适合LSTM这类以权重访存为主的层。
环境依赖
Python 3.8+,PyTorch 1.8+(推荐2.x),NumPy。无需额外数据集——使用随机生成的字符序列模拟训练输入,以聚焦量化流程本身。
完整实现
import torch
import torch.nn as nn
import time
import copy
torch.manual_seed(42)
# ---------- 1. 定义LSTM语言模型 ----------
class LSTMLanguageModel(nn.Module):
def __init__(self, vocab_size=50, embed_dim=64, hidden_dim=128, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_dim, vocab_size)
def forward(self, x):
x = self.embedding(x)
out, _ = self.lstm(x)
return self.fc(out)
# ---------- 2. 初始化模型并生成模拟训练数据 ----------
model = LSTMLanguageModel()
model.train()
# 模拟一批训练数据:batch=8, seq_len=32
x = torch.randint(0, 50, (8, 32))
y = torch.randint(0, 50, (8, 32))
# 简单训练几步使权重非随机(便于量化对比)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.CrossEntropyLoss()
for _ in range(5):
optimizer.zero_grad()
out = model(x)
loss = loss_fn(out.reshape(-1, 50), y.reshape(-1))
loss.backward()
optimizer.step()
model.eval()
# ---------- 3. 应用动态量化 ----------
quantized_model = copy.deepcopy(model)
quantized_model = torch.quantization.quantize_dynamic(
quantized_model, {nn.LSTM, nn.Linear}, dtype=torch.qint8
)
# ---------- 4. 推理速度与内存对比 ----------
def measure(model, input_seq, repeat=20):
# 预热
with torch.no_grad():
model(input_seq)
# 计时
torch.cuda.synchronize() if torch.cuda.is_available() else None
start = time.perf_counter()
with torch.no_grad():
for _ in range(repeat):
model(input_seq)
elapsed = (time.perf_counter() - start) / repeat
# 内存(模型参数+缓冲区)
mem_bytes = sum(p.numel() * p.element_size() for p in model.parameters())
mem_bytes += sum(b.numel() * b.element_size() for b in model.buffers())
return elapsed * 1000, mem_bytes / 1024 # ms, KB
test_input = torch.randint(0, 50, (1, 32))
fp32_time, fp32_mem = measure(model, test_input)
int8_time, int8_mem = measure(quantized_model, test_input)
# ---------- 5. 验证输出一致性 ----------
with torch.no_grad():
out_fp32 = model(test_input)
out_int8 = quantized_model(test_input)
max_diff = (out_fp32 - out_int8).abs().max().item()
print(f"FP32 推理耗时: {fp32_time:.3f} ms/样本")
print(f"INT8 推理耗时: {int8_time:.3f} ms/样本")
print(f"FP32 模型内存: {fp32_mem:.2f} KB")
print(f"INT8 模型内存: {int8_mem:.2f} KB")
print(f"输出最大差异: {max_diff:.6f}")
运行方式
将上述代码保存为 dynamic_quant_lstm.py,在终端执行:
python dynamic_quant_lstm.py
验证方法与判定标准
- 功能正确性:程序无报错正常退出,且打印的
输出最大差异小于0.1(INT8量化对输出影响极小,说明量化模型功能保持)。 - 内存减少:
INT8 模型内存应明显小于FP32 模型内存(通常降低约40%~50%,因LSTM与Linear权重占主导)。 - 速度提升:
INT8 推理耗时应低于FP32 推理耗时。若差异不明显,可增大repeat次数或使用更大hidden_dim(如256)以放大差异。
预期输出示例
FP32 推理耗时: 2.847 ms/样本
INT8 推理耗时: 1.923 ms/样本
FP32 模型内存: 214.34 KB
INT8 模型内存: 112.18 KB
输出最大差异: 0.003821
注:以上数值仅为逻辑一致的示例,实际值因CPU型号、PyTorch版本而异,但内存下降比例与输出差异量级应保持一致。
常见问题
- 速度不升反降:输入序列过短或模型过小导致量化开销占比高。增大
hidden_dim至256或seq_len至64后重试。 - 量化后精度骤降:确认模型已调用
eval();若仍异常,检查是否误量化了Embedding层(动态量化仅支持LSTM和Linear)。 - 内存未明显减少:确认模型权重确实以INT8存储——打印
quantized_model.lstm.weight_ih_l0.dtype应显示torch.qint8。