深度学习模型压缩全景指南:量化、蒸馏、剪枝与LoRA高效微调
目录
2.2.1 训练后量化(Post-Training Quantization, PTQ)
2.2.2 量化感知训练(Quantization-Aware Training, QAT)
2.2.3 动态量化(Dynamic Quantization)
2.3.4 使用GPTQ进行LLM INT4量化(Hugging Face生态)
2.3.5 使用bitsandbytes进行LLM量化(QLoRA常用方案)
三、模型蒸馏(Knowledge Distillation)
3.2 硬标签蒸馏(Hard Label Distillation)
3.3 软标签蒸馏(Soft Label Distillation)
3.4.1 Softmax with Temperature
3.5.3 特征蒸馏(Feature-based Distillation)
4.2 非结构化剪枝(Unstructured Pruning)
4.4.3 迭代剪枝(Iterative Magnitude Pruning)
5.4.2 使用Hugging Face PEFT库(推荐方案)
一、模型压缩概述
1.1 为什么需要模型压缩?
随着深度学习的发展,模型参数量从百万级飙升到千亿级。GPT-3拥有1750亿参数,LLaMA-70B拥有700亿参数。这些大模型在精度上表现优异,但在实际部署中面临严峻挑战:
| 挑战维度 | 具体问题 |
|---|---|
| 存储开销 | 70B模型以FP16存储需要约140GB显存 |
| 推理延迟 | 大模型单次推理耗时长,无法满足实时场景 |
| 硬件成本 | 需要多张高端GPU(如A100/H100)才能运行 |
| 能耗问题 | 大模型推理功耗高,边缘设备无法承受 |
| 部署限制 | 移动端、嵌入式设备内存和算力极其有限 |
1.2 模型压缩技术全景
模型压缩
├── 量化(Quantization) → 降低数值精度(FP32→INT8/INT4)
├── 蒸馏(Distillation) → 大模型指导小模型学习
├── 剪枝(Pruning) → 移除冗余参数/结构
├── 低秩分解(Low-Rank) → LoRA等参数高效微调方法
└── 其他技术
├── 权重共享(Weight Sharing)
├── 神经架构搜索(NAS)
└── 混合专家模型(MoE)
1.3 压缩的核心目标
模型压缩的本质是在 精度(Accuracy) 和 效率(Efficiency) 之间寻找最优平衡点:
目标函数:min Compression_Ratio s.t. Accuracy_Drop ≤ ε
二、模型量化(Model Quantization)
2.1 量化的核心概念
量化是将模型权重和/或激活值从高精度浮点数(如FP32)映射到低精度表示(如INT8、INT4)的过程。
2.1.1 基本原理
量化的核心是一个线性映射函数:
量化(Quantize): x_q = round(x / S) + Z
反量化(Dequantize):x ≈ S × (x_q - Z)
其中:
S(Scale)= 缩放因子Z(Zero Point)= 零点偏移x_q= 量化后的整数值
2.1.2 对称量化 vs 非对称量化
对称量化(Symmetric Quantization):
S = max(|x|) / (2^(b-1) - 1)
Z = 0
x_q = round(x / S)
特点:零点固定为0,计算更简单,适用于权重分布对称的场景。
非对称量化(Asymmetric Quantization):
S = (max(x) - min(x)) / (2^b - 1)
Z = round(-min(x) / S)
x_q = round(x / S) + Z
特点:能更好地适应非对称分布(如ReLU后的激活值),精度更高。
2.1.3 常见量化精度
| 量化位宽 | 表示范围 | 模型大小(相对FP32) | 典型应用场景 |
|---|---|---|---|
| FP32 | ±3.4×10³⁸ | 1×(基准) | 训练 |
| FP16/BF16 | ±6.5×10⁴ | 0.5× | 训练/推理 |
| INT8 | -128 ~ 127 | 0.25× | 服务端推理 |
| INT4 | -8 ~ 7 | 0.125× | 边缘端部署 |
| Binary | {-1, +1} | 0.03125× | 极端压缩研究 |
2.2 三种主流量化方式
2.2.1 训练后量化(Post-Training Quantization, PTQ)
定义:在模型训练完成后,直接对已训练好的模型权重进行量化,无需重新训练。
工作流程:
训练好的FP32模型 → 收集校准数据 → 统计权重/激活值分布 → 确定量化参数(S,Z) → 量化模型 → 验证精度
优点:
- 实现简单,无需训练代码
- 量化速度快(通常几分钟到几小时)
- 适合快速评估量化效果
缺点:
- 精度损失相对较大(尤其在低比特量化时)
- 对离群值(outlier)敏感
- 需要校准数据集
适用场景:模型精度对量化不敏感的任务,或快速部署需求。
2.2.2 量化感知训练(Quantization-Aware Training, QAT)
定义:在训练过程中模拟量化操作,让模型在训练阶段就适应量化带来的精度损失。
核心思想:在前向传播中插入伪量化节点(Fake Quantization),模拟量化误差;反向传播时使用直通估计器(Straight-Through Estimator, STE) 来近似量化函数的梯度。
STE梯度近似:
# 量化函数不可导,STE将其梯度近似为1
# forward: x_q = quantize(x)
# backward: grad_x ≈ grad_x_q (直接传递梯度)
工作流程:
FP32模型 → 插入伪量化节点 → 正常训练/微调 → 前向传播模拟量化 → 反向传播用STE → 得到量化友好模型 → 真正量化
优点:
- 精度损失最小
- 模型学习适应量化误差
- 适合低比特(INT4/INT2)量化
缺点:
- 需要训练数据和训练时间
- 实现复杂度较高
- 需要修改训练流程
适用场景:对精度要求极高且可接受训练开销的场景。
2.2.3 动态量化(Dynamic Quantization)
定义:在推理时动态计算激活值的量化参数,权重在加载时静态量化。
工作流程:
权重:加载时一次性量化(静态)
激活值:每次推理时根据实际输入动态计算S和Z
优点:
- 无需校准数据
- 实现最简单
- 激活值量化更精确(每次都重新计算范围)
缺点:
- 推理时有额外计算开销(动态计算量化参数)
- 不如静态量化的加速效果好
适用场景:没有校准数据集,或输入分布变化大的场景(如NLP中的变长序列)。
三种量化方式对比
| 特性 | PTQ | QAT | 动态量化 |
|---|---|---|---|
| 是否需要训练 | 否 | 是 | 否 |
| 是否需要校准数据 | 是 | 是 | 否 |
| 精度损失 | 中等 | 最小 | 较小 |
| 实现复杂度 | 低 | 高 | 最低 |
| 量化速度 | 快 | 慢 | 最快 |
| 适用比特数 | INT8+ | INT4+ | INT8+ |
| 推理加速效果 | 好 | 好 | 一般 |
2.3 量化代码实战
2.3.1 PTQ训练后量化(PyTorch实现)
import torch
import torch.quantization as quant
import torch.nn as nn
from torchvision import models, transforms
from torch.utils.data import DataLoader
import copy
# ========================
# 1. 准备模型和数据
# ========================
# 加载预训练的MobileNetV2
model_fp32 = models.mobilenet_v2(pretrained=True)
model_fp32.eval()
# 准备校准数据集
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
# 这里用随机数据演示,实际应用中使用真实校准数据
calibration_loader = DataLoader(
dataset=torch.utils.data.TensorDataset(
torch.randn(100, 3, 224, 224),
torch.randint(0, 1000, (100,))
),
batch_size=16
)
# ========================
# 2. 配置量化方案
# ========================
# 方法一:使用torch.quantization的内置API
model_to_quantize = copy.deepcopy(model_fp32)
# 指定量化配置
model_to_quantize.qconfig = torch.quantization.get_default_qconfig('x86')
# 或使用fbgemm后端(x86服务器) / qnnpack后端(ARM移动端)
# 准备模型:插入Observer来统计激活值分布
model_prepared = torch.quantization.prepare(model_to_quantize)
# ========================
# 3. 校准(Calibration)
# ========================
print("开始校准...")
with torch.no_grad():
for batch_idx, (images, _) in enumerate(calibration_loader):
model_prepared(images)
if batch_idx >= 10: # 通常100-500个batch足够
break
print("校准完成!")
# ========================
# 4. 转换为量化模型
# ========================
model_int8 = torch.quantization.convert(model_prepared)
model_int8.eval()
# ========================
# 5. 模型大小对比
# ========================
def get_model_size(model, name="Model"):
torch.save(model.state_dict(), "temp.p")
import os
size_mb = os.path.getsize("temp.p") / 1e6
os.remove("temp.p")
print(f"{name} 大小: {size_mb:.2f} MB")
return size_mb
size_fp32 = get_model_size(model_fp32, "FP32模型")
size_int8 = get_model_size(model_int8, "INT8模型")
print(f"压缩比: {size_fp32 / size_int8:.2f}x")
# ========================
# 6. 精度验证
# ========================
# 用测试数据对比FP32和INT8的输出差异
test_input = torch.randn(1, 3, 224, 224)
with torch.no_grad():
out_fp32 = model_fp32(test_input)
out_int8 = model_int8(test_input)
cosine_sim = torch.nn.functional.cosine_similarity(
out_fp32.flatten(), out_int8.flatten(), dim=0
)
print(f"输出余弦相似度: {cosine_sim:.4f}")
2.3.2 QAT量化感知训练(PyTorch实现)
import torch
import torch.nn as nn
import torch.optim as optim
import torch.quantization as quant
import copy
# ========================
# 1. 定义一个简单的CNN模型
# ========================
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super(SimpleCNN, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32), # QAT兼容的BN
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d(1),
)
self.classifier = nn.Linear(128, num_classes)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x
# ========================
# 2. 先正常预训练
# ========================
model = SimpleCNN(num_classes=10)
# ... 假设已经预训练完成 ...
# ========================
# 3. 配置QAT
# ========================
model.qconfig = torch.quantization.get_default_qat_qconfig('x86')
# 插入伪量化节点
model_prepared = torch.quantization.prepare_qat(model.train())
# ========================
# 4. QAT微调训练
# ========================
optimizer = optim.SGD(model_prepared.parameters(), lr=0.001, momentum=0.9)
criterion = nn.CrossEntropyLoss()
# 模拟训练数据
train_loader = DataLoader(
dataset=torch.utils.data.TensorDataset(
torch.randn(200, 3, 32, 32),
torch.randint(0, 10, (200,))
),
batch_size=32
)
num_epochs = 5
for epoch in range(num_epochs):
model_prepared.train()
total_loss = 0
for images, labels in train_loader:
optimizer.zero_grad()
outputs = model_prepared(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")
# ========================
# 5. 转换为真正的量化模型
# ========================
model_prepared.eval()
model_int8 = torch.quantization.convert(model_prepared)
# 验证量化后模型的输出
test_input = torch.randn(1, 3, 32, 32)
with torch.no_grad():
output = model_int8(test_input)
print(f"量化模型输出shape: {output.shape}")
print(f"预测类别: {output.argmax(dim=1).item()}")
# 检查量化后的模型结构
print("\n量化模型结构:")
print(model_int8)
2.3.3 动态量化(PyTorch实现)
import torch
import torch.nn as nn
import torch.quantization as quant
# ========================
# 1. 定义LSTM模型(动态量化特别适合RNN)
# ========================
class LSTMModel(nn.Module):
def __init__(self, vocab_size=10000, embed_dim=256,
hidden_dim=512, num_layers=2, num_classes=5):
super(LSTMModel, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim,
num_layers=num_layers,
batch_first=True,
bidirectional=True)
self.fc = nn.Linear(hidden_dim * 2, num_classes)
def forward(self, x):
embedded = self.embedding(x)
lstm_out, (h_n, c_n) = self.lstm(embedded)
# 取最后一个时间步
output = self.fc(lstm_out[:, -1, :])
return output
# ========================
# 2. 加载预训练模型
# ========================
model_fp32 = LSTMModel()
model_fp32.eval()
# ========================
# 3. 动态量化 —— 一行代码搞定!
# ========================
model_dynamic = torch.quantization.quantize_dynamic(
model_fp32,
{nn.LSTM, nn.Linear}, # 指定要量化的层类型
dtype=torch.qint8 # 量化到INT8
)
# ========================
# 4. 对比
# ========================
def count_parameters(model):
return sum(p.numel() for p in model.parameters())
def get_model_size(model):
torch.save(model.state_dict(), "temp.p")
import os
size = os.path.getsize("temp.p") / 1e6
os.remove("temp.p")
return size
print(f"FP32 参数量: {count_parameters(model_fp32):,}")
print(f"FP32 模型大小: {get_model_size(model_fp32):.2f} MB")
print(f"动态量化模型大小: {get_model_size(model_dynamic):.2f} MB")
print(f"压缩比: {get_model_size(model_fp32) / get_model_size(model_dynamic):.2f}x")
# ========================
# 5. 推理测试
# ========================
test_input = torch.randint(0, 10000, (1, 50)) # batch=1, seq_len=50
with torch.no_grad():
out_fp32 = model_fp32(test_input)
out_dynamic = model_dynamic(test_input)
print(f"FP32输出: {out_fp32[0][:5]}")
print(f"动态量化输出: {out_dynamic[0][:5]}")
2.3.4 使用GPTQ进行LLM INT4量化(Hugging Face生态)
# ========================
# 使用AutoGPTQ对大语言模型进行INT4量化
# ========================
# pip install auto-gptq transformers accelerate
from transformers import AutoModelForCausalLM, AutoTokenizer
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
# ========================
# 1. 配置量化参数
# ========================
quantize_config = BaseQuantizeConfig(
bits=4, # 量化位数:4bit
group_size=128, # 分组大小
damp_percent=0.01, # Hessian矩阵正则化
desc_act=True, # 按激活值重要性排序(更精确)
sym=False, # 非对称量化
)
# ========================
# 2. 加载模型和分词器
# ========================
model_id = "facebook/opt-1.3b"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoGPTQForCausalLM.from_pretrained(
model_id,
quantize_config,
device_map="auto"
)
# ========================
# 3. 准备校准数据
# ========================
# 使用WikiText或其他校准数据
calibration_data = [
"The quick brown fox jumps over the lazy dog.",
"Machine learning is a subset of artificial intelligence.",
"Deep neural networks have revolutionized computer vision.",
# ... 实际使用时需要几百到几千条样本
]
# Tokenize校准数据
calibration_dataset = [
tokenizer(text, return_tensors="pt", max_length=512, truncation=True)
for text in calibration_data
]
# ========================
# 4. 执行量化
# ========================
print("开始GPTQ量化...")
model.quantize(calibration_dataset)
print("量化完成!")
# ========================
# 5. 保存量化模型
# ========================
model.save_quantized("./opt-1.3b-gptq-4bit")
tokenizer.save_pretrained("./opt-1.3b-gptq-4bit")
# ========================
# 6. 加载量化模型推理
# ========================
quantized_model = AutoGPTQForCausalLM.from_quantized(
"./opt-1.3b-gptq-4bit",
device="cuda:0"
)
input_text = "The future of artificial intelligence is"
inputs = tokenizer(input_text, return_tensors="pt").to("cuda:0")
outputs = quantized_model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
2.3.5 使用bitsandbytes进行LLM量化(QLoRA常用方案)
# ========================
# 使用bitsandbytes进行4-bit/8-bit量化加载
# ========================
# pip install bitsandbytes accelerate transformers
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
# ========================
# 1. 8-bit量化加载
# ========================
model_8bit = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
load_in_8bit=True, # 启用8-bit量化
device_map="auto",
torch_dtype=torch.float16,
)
# ========================
# 2. 4-bit量化加载(NF4格式,QLoRA标配)
# ========================
bnb_config_4bit = BitsAndBytesConfig(
load_in_4bit=True, # 启用4-bit量化
bnb_4bit_quant_type="nf4", # NormalFloat4量化
bnb_4bit_compute_dtype=torch.bfloat16, # 计算时的数据类型
bnb_4bit_use_double_quant=True, # 二次量化,进一步压缩
)
model_4bit = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config_4bit,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# ========================
# 3. 对比显存占用
# ========================
def print_gpu_memory(model, name):
total_params = sum(p.numel() for p in model.parameters())
print(f"\n{name}:")
print(f" 参数量: {total_params / 1e9:.2f}B")
# 估算显存
mem_bytes = sum(
p.numel() * p.element_size() for p in model.parameters()
)
print(f" 模型显存: {mem_bytes / 1e9:.2f} GB")
# ========================
# 4. 推理测试
# ========================
prompt = "Explain quantum computing in simple terms:"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
with torch.no_grad():
outputs = model_4bit.generate(
**inputs,
max_new_tokens=200,
temperature=0.7,
top_p=0.9,
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
2.4 量化精度与性能对比
┌─────────────┬──────────┬──────────┬──────────────┬──────────────┐
│ 量化方式 │ 模型大小 │ 推理速度 │ 精度损失 │ 适用场景 │
├─────────────┼──────────┼──────────┼──────────────┼──────────────┤
│ FP32 │ 1.0× │ 1.0× │ 基准 │ 训练 │
│ FP16 │ 0.5× │ 1.5~2× │ <0.1% │ 训练/推理 │
│ INT8 PTQ │ 0.25× │ 2~4× │ 0.5~2% │ 服务端推理 │
│ INT8 QAT │ 0.25× │ 2~4× │ <0.5% │ 高精度推理 │
│ INT4 GPTQ │ 0.125× │ 3~5× │ 1~3% │ LLM部署 │
│ Binary │ 0.03× │ 10~20× │ 5~15% │ 研究/极端场景│
└─────────────┴──────────┴──────────┴──────────────┴──────────────┘
三、模型蒸馏(Knowledge Distillation)
3.1 蒸馏的核心概念
知识蒸馏(Knowledge Distillation) 由Hinton等人在2015年提出,核心思想是用一个大模型(Teacher模型) 来指导一个小模型(Student模型) 学习,让小模型尽可能逼近大模型的表现。
3.1.1 为什么蒸馏有效?
大模型(Teacher)学到了丰富的"暗知识(Dark Knowledge)"——即类别之间的相似性关系。例如在图像分类中:
Teacher对一张猫的图片的输出概率:
猫: 0.85, 豹: 0.10, 狗: 0.04, 鱼: 0.01
硬标签(Ground Truth):
猫: 1.0, 豹: 0.0, 狗: 0.0, 鱼: 0.0
Teacher的概率分布告诉我们:猫和豹很像,猫和狗也有一些相似性,猫和鱼差异很大。这些关系信息在硬标签中完全丢失了。
3.1.2 蒸馏的基本架构
┌──────────────────┐
│ Teacher模型 │
│ (大模型,参数冻结) │
└────────┬─────────┘
│
软标签/硬标签
│
┌────────▼─────────┐
输入数据 ────→│ Student模型 │──→ 最终部署
│ (小模型,参数训练) │
└──────────────────┘
3.2 硬标签蒸馏(Hard Label Distillation)
3.2.1 定义
硬标签蒸馏是指Student模型学习Teacher模型的最终预测类别(argmax),即学习Teacher的"硬决策"。
损失函数:
L_hard = L_CE(y_student, y_teacher_hard)
其中 y_teacher_hard = argmax(p_teacher)
3.2.2 特点
| 优点 | 缺点 |
|---|---|
| 实现简单 | 丢失了Teacher的概率分布信息 |
| 不需要温度参数 | 无法传递类别间的关系知识 |
| 训练稳定 | 蒸馏效果不如软标签 |
3.2.3 适用场景
- Teacher模型的预测非常置信(概率接近one-hot)
- 分类任务中类别数较少
- 需要快速实现基线方案
3.3 软标签蒸馏(Soft Label Distillation)
3.3.1 定义
软标签蒸馏是蒸馏的核心方法,Student模型同时学习:
- 1.Teacher的软标签(经过温度缩放的概率分布)
- 2.Ground Truth硬标签
损失函数:
L_total = α × L_soft + (1 - α) × L_hard
L_soft = KL_Divergence(softmax(z_s / T), softmax(z_t / T)) × T²
L_hard = CrossEntropy(y_student, y_true)
其中:
z_s= Student的logitsz_t= Teacher的logitsT= 温度参数(Temperature)α= 软标签损失的权重系数T²= 缩放因子,补偿温度对梯度的影响
3.4 温度参数T的数学原理
3.4.1 Softmax with Temperature
标准Softmax:
p_i = exp(z_i) / Σ_j exp(z_j)
带温度的Softmax:
p_i = exp(z_i / T) / Σ_j exp(z_j / T)
- T = 1:标准Softmax,概率分布尖锐
- T > 1:概率分布变平滑,暴露更多类别间关系
- T → ∞:均匀分布,所有类别概率相等
- T → 0:退化为argmax,硬标签
3.4.2 温度的效果示意
假设Teacher的logits = [5.0, 3.0, 1.0, 0.1]
T=1: [0.843, 0.114, 0.015, 0.006] → 猫很确定是第一个
T=3: [0.545, 0.285, 0.115, 0.055] → 显示出第一和第二的关联
T=5: [0.443, 0.304, 0.160, 0.093] → 更平滑,关系更清晰
T=10: [0.371, 0.302, 0.200, 0.127] → 非常平滑
实践建议:
- 通常T取 2~20 之间
- 分类类别数多时用较大的T
- T过大会引入过多噪声
- 可以通过验证集调优T
3.5 蒸馏代码实战
3.5.1 硬标签蒸馏
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import DataLoader, TensorDataset
# ========================
# 1. 定义Teacher和Student模型
# ========================
class TeacherModel(nn.Module):
"""Teacher: 较大的模型"""
def __init__(self, input_dim=784, num_classes=10):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 1024),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(1024, 512),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, num_classes),
)
def forward(self, x):
return self.net(x)
class StudentModel(nn.Module):
"""Student: 较小的模型"""
def __init__(self, input_dim=784, num_classes=10):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, num_classes),
)
def forward(self, x):
return self.net(x)
# ========================
# 2. 硬标签蒸馏训练
# ========================
def train_hard_label_distillation(teacher, student, train_loader,
num_epochs=10, lr=0.001):
teacher.eval() # Teacher不训练
student.train()
optimizer = optim.Adam(student.parameters(), lr=lr)
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
total_loss = 0
correct = 0
total = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
# Teacher的预测(硬标签)
with torch.no_grad():
teacher_logits = teacher(inputs)
teacher_hard_labels = teacher_logits.argmax(dim=1)
# Student的预测
student_logits = student(inputs)
# 硬标签蒸馏损失:Student学习Teacher的预测类别
loss = criterion(student_logits, teacher_hard_labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
_, predicted = student_logits.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
acc = 100. * correct / total
avg_loss = total_loss / len(train_loader)
print(f"Epoch [{epoch+1}/{num_epochs}] "
f"Loss: {avg_loss:.4f} Acc: {acc:.2f}%")
# 准备数据
train_data = torch.randn(1000, 784)
train_labels = torch.randint(0, 10, (1000,))
train_loader = DataLoader(
TensorDataset(train_data, train_labels),
batch_size=64, shuffle=True
)
# 初始化模型
teacher = TeacherModel()
student = StudentModel()
# 假设Teacher已经预训练好
# ... teacher训练过程省略 ...
print("=== 硬标签蒸馏训练 ===")
train_hard_label_distillation(teacher, student, train_loader)
3.5.2 软标签蒸馏(经典实现)
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import DataLoader, TensorDataset
# ========================
# 核心:软标签蒸馏损失函数
# ========================
class DistillationLoss(nn.Module):
"""
经典知识蒸馏损失
L = α * T² * KL(softmax(z_s/T) || softmax(z_t/T)) + (1-α) * CE(z_s, y)
"""
def __init__(self, temperature=4.0, alpha=0.7):
super().__init__()
self.temperature = temperature
self.alpha = alpha
self.ce_loss = nn.CrossEntropyLoss()
self.kl_loss = nn.KLDivLoss(reduction='batchmean')
def forward(self, student_logits, teacher_logits, labels):
# 软标签损失
soft_student = F.log_softmax(student_logits / self.temperature, dim=1)
soft_teacher = F.softmax(teacher_logits / self.temperature, dim=1)
soft_loss = self.kl_loss(soft_student, soft_teacher) * (self.temperature ** 2)
# 硬标签损失
hard_loss = self.ce_loss(student_logits, labels)
# 加权组合
total_loss = self.alpha * soft_loss + (1 - self.alpha) * hard_loss
return total_loss, soft_loss.item(), hard_loss.item()
# ========================
# 软标签蒸馏训练流程
# ========================
def train_soft_label_distillation(teacher, student, train_loader,
temperature=4.0, alpha=0.7,
num_epochs=20, lr=0.001):
teacher.eval()
student.train()
optimizer = optim.Adam(student.parameters(), lr=lr)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)
distill_criterion = DistillationLoss(temperature=temperature, alpha=alpha)
for epoch in range(num_epochs):
total_loss = 0
total_soft = 0
total_hard = 0
correct = 0
total = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
# Teacher前向传播
with torch.no_grad():
teacher_logits = teacher(inputs)
# Student前向传播
student_logits = student(inputs)
# 计算蒸馏损失
loss, soft_l, hard_l = distill_criterion(
student_logits, teacher_logits, labels
)
loss.backward()
optimizer.step()
total_loss += loss.item()
total_soft += soft_l
total_hard += hard_l
_, predicted = student_logits.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
scheduler.step()
acc = 100. * correct / total
avg_loss = total_loss / len(train_loader)
avg_soft = total_soft / len(train_loader)
avg_hard = total_hard / len(train_loader)
print(f"Epoch [{epoch+1}/{num_epochs}] "
f"Total: {avg_loss:.4f} "
f"Soft: {avg_soft:.4f} "
f"Hard: {avg_hard:.4f} "
f"Acc: {acc:.2f}%")
# ========================
# 运行蒸馏
# ========================
teacher = TeacherModel()
student = StudentModel()
train_data = torch.randn(1000, 784)
train_labels = torch.randint(0, 10, (1000,))
train_loader = DataLoader(
TensorDataset(train_data, train_labels),
batch_size=64, shuffle=True
)
print("=== 软标签蒸馏训练 ===")
train_soft_label_distillation(
teacher, student, train_loader,
temperature=4.0, alpha=0.7, num_epochs=20
)
3.5.3 特征蒸馏(Feature-based Distillation)
# ========================
# 特征蒸馏:Student学习Teacher的中间层特征
# 适用于Teacher和Student结构差异较大的情况
# ========================
class FeatureDistillationLoss(nn.Module):
"""
让Student的中间层特征对齐Teacher的中间层特征
"""
def __init__(self, teacher_dims, student_dims):
super().__init__()
# 如果维度不匹配,用线性层对齐
self.projectors = nn.ModuleList([
nn.Linear(s_dim, t_dim)
for s_dim, t_dim in zip(student_dims, teacher_dims)
])
def forward(self, student_features, teacher_features):
loss = 0
for i, (s_feat, t_feat) in enumerate(
zip(student_features, teacher_features)
):
# 投影对齐
s_feat_proj = self.projectors[i](s_feat)
# MSE损失
loss += F.mse_loss(s_feat_proj, t_feat)
return loss
class TeacherWithFeatures(nn.Module):
"""支持提取中间特征的Teacher"""
def __init__(self):
super().__init__()
self.layer1 = nn.Linear(784, 1024)
self.layer2 = nn.Linear(1024, 512)
self.layer3 = nn.Linear(512, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
f1 = F.relu(self.layer1(x))
f2 = F.relu(self.layer2(f1))
f3 = F.relu(self.layer3(f2))
out = self.output(f3)
return out, [f1, f2, f3] # 返回特征
class StudentWithFeatures(nn.Module):
"""支持提取中间特征的Student"""
def __init__(self):
super().__init__()
self.layer1 = nn.Linear(784, 256)
self.layer2 = nn.Linear(256, 128)
self.output = nn.Linear(128, 10)
def forward(self, x):
f1 = F.relu(self.layer1(x))
f2 = F.relu(self.layer2(f1))
out = self.output(f2)
return out, [f1, f2] # 返回特征
# 联合训练:分类损失 + 特征蒸馏损失 + 软标签蒸馏损失
def train_with_feature_distillation(teacher, student, train_loader,
feature_criterion, distill_criterion,
feature_weight=0.1,
num_epochs=10, lr=0.001):
teacher.eval()
student.train()
optimizer = optim.Adam(
list(student.parameters()) + list(feature_criterion.parameters()),
lr=lr
)
for epoch in range(num_epochs):
total_loss = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
with torch.no_grad():
t_out, t_features = teacher(inputs)
s_out, s_features = student(inputs)
# 1. 分类损失
cls_loss = F.cross_entropy(s_out, labels)
# 2. 软标签蒸馏损失
soft_loss = distill_criterion(s_out, t_out, labels)[0]
# 3. 特征蒸馏损失
feat_loss = feature_criterion(s_features, t_features)
# 总损失
loss = cls_loss + soft_loss + feature_weight * feat_loss
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch [{epoch+1}/{num_epochs}] "
f"Loss: {total_loss/len(train_loader):.4f}")
3.5.4 使用Hugging Face进行LLM蒸馏
# ========================
# 使用transformers进行BERT蒸馏到TinyBERT
# ========================
# pip install transformers datasets
from transformers import (
AutoModelForSequenceClassification,
AutoTokenizer,
Trainer,
TrainingArguments
)
import torch
import torch.nn as nn
import torch.nn.functional as F
class DistillationTrainer(Trainer):
"""自定义Trainer,支持知识蒸馏"""
def __init__(self, teacher_model=None, temperature=2.0,
alpha=0.5, *args, **kwargs):
super().__init__(*args, **kwargs)
self.teacher = teacher_model
self.temperature = temperature
self.alpha = alpha
if self.teacher:
self.teacher.eval()
def compute_loss(self, model, inputs, return_outputs=False, **kwargs):
labels = inputs.pop("labels")
# Student前向
student_outputs = model(**inputs)
student_logits = student_outputs.logits
# Teacher前向
with torch.no_grad():
teacher_outputs = self.teacher(**inputs)
teacher_logits = teacher_outputs.logits
# 硬标签损失
hard_loss = F.cross_entropy(student_logits, labels)
# 软标签损失
soft_student = F.log_softmax(student_logits / self.temperature, dim=1)
soft_teacher = F.softmax(teacher_logits / self.temperature, dim=1)
soft_loss = F.kl_div(
soft_student, soft_teacher,
reduction='batchmean'
) * (self.temperature ** 2)
# 总损失
loss = self.alpha * soft_loss + (1 - self.alpha) * hard_loss
return (loss, student_outputs) if return_outputs else loss
# 使用示例
teacher_model = AutoModelForSequenceClassification.from_pretrained(
"bert-large-uncased", num_labels=2
)
student_model = AutoModelForSequenceClassification.from_pretrained(
"prajjwal1/bert-tiny", num_labels=2 # 极小的BERT
)
tokenizer = AutoTokenizer.from_pretrained("bert-large-uncased")
print(f"Teacher参数量: {sum(p.numel() for p in teacher_model.parameters()):,}")
print(f"Student参数量: {sum(p.numel() for p in student_model.parameters()):,}")
print(f"压缩比: {sum(p.numel() for p in teacher_model.parameters()) / sum(p.numel() for p in student_model.parameters()):.1f}x")
3.6 蒸馏策略对比
┌──────────────────┬───────────────┬──────────────────┬─────────────────┐
│ 蒸馏策略 │ 信息来源 │ 损失函数 │ 适用场景 │
├──────────────────┼───────────────┼──────────────────┼─────────────────┤
│ 硬标签蒸馏 │ Teacher预测类别│ CE(y_s, y_t_hard)│ 类别少/快速基线 │
│ 软标签蒸馏 │ Teacher概率分布│ KL(p_s/T||p_t/T) │ 通用分类任务 │
│ 特征蒸馏 │ Teacher中间层 │ MSE(f_s, f_t) │ 结构差异大 │
│ 关系蒸馏 │ 样本间关系 │ 保持样本间距离关系 │ 数据关系重要 │
│ 自蒸馏 │ 模型自身 │ 深层指导浅层 │ 无大Teacher时 │
└──────────────────┴───────────────┴──────────────────┴─────────────────┘
四、模型剪枝(Model Pruning)
4.1 剪枝的核心概念
模型剪枝通过移除模型中不重要(冗余)的参数或结构,在保持精度的前提下减少模型大小和计算量。
4.1.1 核心假设
深度学习模型通常过参数化(Over-parameterized),大量参数对最终预测贡献很小。研究表明,许多模型可以剪掉90%以上的参数而精度几乎不变("彩票假设" Lottery Ticket Hypothesis)。
4.1.2 剪枝的分类
剪枝方法
├── 按粒度分
│ ├── 非结构化剪枝(Unstructured)→ 单个权重级别
│ └── 结构化剪枝(Structured) → 整个通道/层级别
│
├── 按时机分
│ ├── 训练后剪枝(Post-training) → 训练完成后剪枝
│ ├── 训练中剪枝(During training)→ 训练过程中逐渐剪枝
│ └── 训练前剪枝(Before training)→ 初始化时就确定结构
│
└── 按策略分
├── 一次性剪枝(One-shot) → 一次性剪到目标稀疏度
└── 迭代剪枝(Iterative) → 多轮剪枝+微调循环
4.1.3 重要性评估标准
| 标准 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 幅值剪枝 | 移除绝对值最小的权重 | 实现最简单 | 可能错过小但重要的权重 |
| 梯度剪枝 | 移除梯度绝对值小的权重 | 考虑了对损失的影响 | 需要额外计算梯度 |
| Taylor展开 | 用一阶/二阶Taylor近似重要性 | 理论更严谨 | 计算开销大 |
| Hessian信息 | 用Hessian矩阵衡量重要性 | 最精确 | 计算非常昂贵 |
| Activation | 移除激活值接近零的神经元 | 直觉清晰 | 依赖输入数据 |
4.2 非结构化剪枝(Unstructured Pruning)
4.2.1 定义
逐个移除权重矩阵中绝对值最小的权重,将其置为0,形成稀疏矩阵。
原始权重矩阵W: 剪枝后W':
[0.5, 0.01, -0.3] [0.5, 0, -0.3]
[0.02, 0.8, 0.001] → [0, 0.8, 0 ]
[-0.1, 0.005, 0.4] [-0.1, 0, 0.4 ]
稀疏度: 0% 稀疏度: 44.4%
4.2.2 特点
- 优点:精度保持好,灵活度高
- 缺点:稀疏矩阵需要专用硬件/库加速(如NVIDIA Ampere的稀疏张量核心),否则实际加速有限
- 适用场景:研究场景,或有稀疏计算硬件支持时
4.3 结构化剪枝(Structured Pruning)
4.3.1 定义
移除整个卷积通道(filter)、注意力头或整个层,得到的模型仍然是稠密的,无需特殊硬件支持。
原始: Conv2d(in=64, out=128, kernel=3×3)
→ 128个filter, 每个filter 64×3×3 = 576个参数
结构化剪枝: 移除40个不重要的filter
→ Conv2d(in=64, out=88, kernel=3×3)
→ 88个filter, 参数减少31%
4.3.2 特点
- 优点:直接减少计算量,无需特殊硬件,实际加速效果好
- 缺点:剪枝粒度粗,同等压缩比下精度损失更大
- 适用场景:生产环境部署
4.4 剪枝代码实战
4.4.1 非结构化剪枝(PyTorch内置API)
import torch
import torch.nn as nn
import torch.nn.utils.prune as prune
import copy
# ========================
# 1. 定义模型
# ========================
class ConvNet(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 3, padding=1)
self.bn1 = nn.BatchNorm2d(64)
self.conv2 = nn.Conv2d(64, 128, 3, padding=1)
self.bn2 = nn.BatchNorm2d(128)
self.conv3 = nn.Conv2d(128, 256, 3, padding=1)
self.bn3 = nn.BatchNorm2d(256)
self.pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(256, num_classes)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.bn1(self.conv1(x)))
x = self.relu(self.bn2(self.conv2(x)))
x = self.relu(self.bn3(self.conv3(x)))
x = self.pool(x).flatten(1)
x = self.fc(x)
return x
model = ConvNet()
# ========================
# 2. 查看剪枝前的参数统计
# ========================
def count_nonzero_params(model):
total = 0
nonzero = 0
for p in model.parameters():
total += p.numel()
nonzero += (p != 0).sum().item()
return total, nonzero
total, nonzero = count_nonzero_params(model)
print(f"剪枝前: 总参数 {total:,}, 非零参数 {nonzero:,}, 稀疏度 {1-nonzero/total:.2%}")
# ========================
# 3. L1非结构化剪枝(全局)
# ========================
# 对所有卷积层和全连接层进行剪枝
parameters_to_prune = [
(model.conv1, 'weight'),
(model.conv2, 'weight'),
(model.conv3, 'weight'),
(model.fc, 'weight'),
]
# 全局剪枝:将所有层的权重放在一起排序,移除最小的50%
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.5, # 剪掉50%
)
total, nonzero = count_nonzero_params(model)
print(f"剪枝后: 总参数 {total:,}, 非零参数 {nonzero:,}, 稀疏度 {1-nonzero/total:.2%}")
# ========================
# 4. 查看剪枝掩码
# ========================
print(f"\nconv1剪枝掩码示例:")
mask = dict(model.conv1.named_buffers()).get('weight_mask', None)
if mask is not None:
print(f" mask shape: {mask.shape}")
print(f" mask非零比例: {(mask > 0).float().mean():.2%}")
# ========================
# 5. 永久化剪枝(移除mask,将0真正写入权重)
# ========================
for module, param_name in parameters_to_prune:
prune.remove(module, param_name)
# 验证稀疏度保持
total, nonzero = count_nonzero_params(model)
print(f"\n永久化后: 总参数 {total:,}, 非零参数 {nonzero:,}, 稀疏度 {1-nonzero/total:.2%}")
# ========================
# 6. 剪枝后微调恢复精度
# ========================
def finetune_after_pruning(model, train_loader, num_epochs=5, lr=0.001):
"""剪枝后微调是恢复精度的关键步骤"""
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
model.train()
total_loss = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Finetune Epoch [{epoch+1}/{num_epochs}] "
f"Loss: {total_loss/len(train_loader):.4f}")
4.4.2 结构化剪枝
import torch
import torch.nn as nn
import torch.nn.utils.prune as prune
# ========================
# 结构化剪枝:移除整个卷积通道
# ========================
model = ConvNet()
# ========================
# 1. 基于L1范数的通道剪枝
# ========================
# 剪掉conv2中30%的输出通道
prune.ln_structured(
model.conv2,
name='weight',
amount=0.3, # 剪掉30%的通道
n=1, # L1范数
dim=0, # 沿输出通道维度剪枝
)
# 查看结果
print(f"conv2 原始输出通道: 128")
mask = dict(model.conv2.named_buffers())['weight_mask']
active_channels = mask[:, 0, 0, 0].sum().item() # 每个filter的mask相同
print(f"conv2 剪枝后活跃通道: {int(active_channels)}")
print(f"conv2 剪枝率: {1 - active_channels/128:.1%}")
# ========================
# 2. 自定义结构化剪枝(基于通道重要性)
# ========================
def channel_importance_pruning(model, layer_name, prune_ratio=0.3):
"""
基于通道L1范数重要性的结构化剪枝
"""
layer = dict(model.named_modules())[layer_name]
weight = layer.weight.data # shape: [out_channels, in_channels, kH, kW]
# 计算每个输出通道的L1范数作为重要性指标
importance = weight.abs().sum(dim=[1, 2, 3]) # shape: [out_channels]
# 确定要保留的通道数
num_channels = weight.shape[0]
num_keep = int(num_channels * (1 - prune_ratio))
# 选择最重要的通道
_, indices = torch.topk(importance, num_keep)
mask = torch.zeros(num_channels, device=weight.device)
mask[indices] = 1.0
# 扩展mask到权重形状
mask = mask.view(-1, 1, 1, 1).expand_as(weight)
# 应用剪枝
layer.weight.data *= mask
print(f"{layer_name}: {num_channels} → {num_keep} 通道 "
f"(剪掉 {prune_ratio:.0%})")
return indices
# 使用
kept_indices = channel_importance_pruning(model, 'conv2', prune_ratio=0.3)
4.4.3 迭代剪枝(Iterative Magnitude Pruning)
import torch
import torch.nn as nn
import torch.nn.utils.prune as prune
# ========================
# 迭代剪枝:多轮剪枝+微调,逐步达到目标稀疏度
# ========================
def iterative_pruning(model, train_loader, val_loader,
target_sparsity=0.9,
num_rounds=10,
finetune_epochs=3,
lr=0.001):
"""
迭代剪枝策略(Lottery Ticket Hypothesis的实践方法)
每一轮:
1. 剪掉当前权重中一定比例的最小权重
2. 微调恢复精度
3. 重复直到达到目标稀疏度
"""
# 每轮剪枝的比例(相对于剩余权重)
prune_per_round = 1 - (1 - target_sparsity) ** (1 / num_rounds)
print(f"目标稀疏度: {target_sparsity:.0%}")
print(f"总轮数: {num_rounds}")
print(f"每轮剪枝比例: {prune_per_round:.2%}\n")
criterion = nn.CrossEntropyLoss()
parameters_to_prune = [
(module, 'weight')
for name, module in model.named_modules()
if isinstance(module, (nn.Conv2d, nn.Linear))
]
for round_idx in range(num_rounds):
# ===== 剪枝 =====
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=prune_per_round,
)
# 统计当前稀疏度
total, nonzero = 0, 0
for module, param_name in parameters_to_prune:
param = getattr(module, param_name)
total += param.numel()
nonzero += (param != 0).sum().item()
current_sparsity = 1 - nonzero / total
print(f"Round {round_idx + 1}/{num_rounds} | "
f"稀疏度: {current_sparsity:.2%}")
# ===== 微调 =====
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
for epoch in range(finetune_epochs):
model.train()
total_loss = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
# 重要:只更新非零权重(保持剪枝结果)
optimizer.step()
avg_loss = total_loss / max(len(train_loader), 1)
# ===== 验证 =====
model.eval()
correct, total = 0, 0
with torch.no_grad():
for inputs, labels in val_loader:
outputs = model(inputs)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
val_acc = 100. * correct / total
print(f" 微调后验证精度: {val_acc:.2f}%\n")
return model
4.5 剪枝与重训练策略
┌─────────────────────────────────────────────────────────┐
│ 剪枝最佳实践流程 │
│ │
│ 1. 训练原始模型至收敛 │
│ ↓ │
│ 2. 评估各层/通道重要性 │
│ ↓ │
│ 3. 剪掉不重要的部分(10~30%) │
│ ↓ │
│ 4. 微调(fine-tune)恢复精度 │
│ ↓ │
│ 5. 重复步骤2-4直到达到目标稀疏度 │
│ ↓ │
│ 6. 导出剪枝后的模型 │
│ │
│ 关键参数: │
│ - 每轮剪枝比例: 10~30%(推荐20%) │
│ - 微调轮数: 1~5 epochs │
│ - 学习率: 原始学习率的1/10~1/100 │
│ - 总剪枝轮数: 5~20轮 │
└─────────────────────────────────────────────────────────┘
五、LoRA低秩矩阵高效微调
5.1 LoRA的核心概念
LoRA(Low-Rank Adaptation of Large Language Models) 由微软于2021年提出,是一种参数高效微调(PEFT, Parameter-Efficient Fine-Tuning)方法。
5.1.1 核心思想
大模型微调时,权重更新矩阵ΔW通常是低秩的(Low-Rank)——即可以用两个更小的矩阵的乘积来近似表示。
原始更新: W_new = W_0 + ΔW ΔW ∈ R^(d×d), 有 d² 个参数
LoRA分解: ΔW = B × A B ∈ R^(d×r), A ∈ R^(r×d), 有 2dr 个参数
其中 r << d
参数减少: 从 d² → 2dr
当 d=4096, r=8 时: 16,777,216 → 65,536 (减少 99.6%)
5.1.2 为什么LoRA有效?
- 1.内在维度假说:预训练模型的权重更新存在一个低维的"内在子空间",不需要更新所有参数
- 2.Aghajanyan et al. (2020) 的实验表明,即使是随机初始化的模型,也存在低维子空间可以达到不错的微调效果
- 3.微调的本质是做任务适配,而非重新学习语言知识
5.2 LoRA的数学原理
5.2.1 前向传播
标准前向传播:
h = W_0 × x
LoRA前向传播:
h = W_0 × x + (B × A) × x
= W_0 × x + B × (A × x)
= W_0 × x + Δh
其中:
W_0: 预训练权重(冻结,不更新)
A ∈ R^(r×d): 下投影矩阵(随机高斯初始化)
B ∈ R^(d×r): 上投影矩阵(零初始化)
r: 秩(rank),通常取 4~64
5.2.2 初始化策略
# A矩阵:随机高斯初始化
A = torch.randn(r, d) * (1 / math.sqrt(r))
# B矩阵:零初始化
B = torch.zeros(d, r)
# 初始时 ΔW = B × A = 0
# 这保证了训练开始时LoRA不影响预训练模型的输出
5.2.3 缩放因子α
h = W_0 × x + (α / r) × B × A × x
α是一个超参数,控制LoRA的更新幅度- 实践中通常设
α = r或α = 2r - 当
α = r时,缩放因子为1
5.2.4 应用位置
在Transformer架构中,LoRA通常应用于注意力层的Q、K、V、O投影矩阵:
Transformer Block
├── Multi-Head Attention
│ ├── Q_proj ← LoRA ✓ (最常见)
│ ├── K_proj ← LoRA ✓
│ ├── V_proj ← LoRA ✓
│ └── O_proj ← LoRA ✓
├── FFN
│ ├── up_proj ← LoRA ✓ (可选)
│ └── down_proj ← LoRA ✓ (可选)
├── LayerNorm
└── Residual Connection
5.3 LoRA vs 全量微调
5.3.1 全面对比
| 对比维度 | 全量微调(Full Fine-tuning) | LoRA微调 |
|---|---|---|
| 可训练参数 | 100%(所有参数) | 0.1%~1%(仅LoRA矩阵) |
| 显存占用 | 非常高(需存储梯度、优化器状态) | 大幅降低(仅LoRA参数的梯度) |
| 训练速度 | 慢 | 快(计算量小) |
| 存储开销 | 每个任务一个完整模型副本 | 仅需存储LoRA权重(几MB~几十MB) |
| 多任务切换 | 需要加载不同模型 | 热插拔LoRA权重 |
| 灾难性遗忘 | 风险较高 | 风险低(预训练权重冻结) |
| 精度 | 通常最好 | 接近全量微调(差距<1%) |
| 适用场景 | 数据充足、计算资源充足 | 资源受限、多任务、快速迭代 |
5.3.2 显存对比示例(LLaMA-7B)
模型: LLaMA-7B (6.7B参数)
全量微调(FP16 + AdamW):
模型参数: 6.7B × 2 bytes = 13.4 GB
梯度: 6.7B × 2 bytes = 13.4 GB
优化器状态: 6.7B × 8 bytes = 53.6 GB (Adam: m + v)
总计: ~80 GB → 需要 2×A100-80GB
LoRA微调(rank=8, FP16):
模型参数(冻结): 13.4 GB
LoRA参数: ~10M × 2 bytes = 0.02 GB
LoRA梯度: ~10M × 2 bytes = 0.02 GB
LoRA优化器状态: ~10M × 8 bytes = 0.08 GB
总计: ~13.5 GB → 单张RTX 3090/4090即可
5.3.3 什么时候用全量微调?
- 训练数据充足(>10万条)
- 任务与预训练分布差异极大(如新语言、新领域)
- 有足够的GPU资源
- 追求极致精度
5.3.4 什么时候用LoRA?
- 训练数据有限(<1万条)
- 需要快速迭代多个任务
- GPU资源有限
- 需要部署多个不同任务的模型(LoRA热插拔)
5.4 LoRA代码实战
5.4.1 从零实现LoRA层
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
# ========================
# 1. LoRA层的PyTorch实现
# ========================
class LoRALayer(nn.Module):
"""
低秩适配层
将一个线性层替换为: W_0 + (α/r) * B @ A
"""
def __init__(self, original_layer, rank=8, alpha=16, dropout=0.1):
super().__init__()
self.original_layer = original_layer
self.rank = rank
self.alpha = alpha
self.scaling = alpha / rank
in_features = original_layer.in_features
out_features = original_layer.out_features
# 冻结原始权重
self.original_layer.weight.requires_grad = False
if self.original_layer.bias is not None:
self.original_layer.bias.requires_grad = False
# LoRA矩阵
self.lora_A = nn.Parameter(
torch.randn(rank, in_features) * (1 / math.sqrt(rank))
)
self.lora_B = nn.Parameter(
torch.zeros(out_features, rank)
)
self.dropout = nn.Dropout(dropout)
# 可训练参数统计
lora_params = rank * in_features + out_features * rank
original_params = in_features * out_features
print(f"LoRA参数: {lora_params:,} / 原始参数: {original_params:,} "
f"({lora_params/original_params:.2%})")
def forward(self, x):
# 原始前向传播
original_output = self.original_layer(x)
# LoRA分支
lora_output = (self.dropout(x) @ self.lora_A.T @ self.lora_B.T) * self.scaling
return original_output + lora_output
# ========================
# 2. 将LoRA应用到整个模型
# ========================
def apply_lora_to_model(model, target_modules=None, rank=8, alpha=16):
"""
对模型中指定的线性层应用LoRA
"""
if target_modules is None:
target_modules = ['query', 'key', 'value', 'dense', 'fc', 'proj']
lora_count = 0
for name, module in model.named_modules():
if isinstance(module, nn.Linear):
# 检查是否为目标模块
if any(target in name for target in target_modules):
parent_name = '.'.join(name.split('.')[:-1])
child_name = name.split('.')[-1]
parent = dict(model.named_modules()).get(
parent_name, model
)
# 替换为LoRA层
lora_layer = LoRALayer(module, rank=rank, alpha=alpha)
setattr(parent, child_name, lora_layer)
lora_count += 1
print(f"\n共为 {lora_count} 个线性层添加了LoRA")
# 统计可训练参数
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"可训练参数: {trainable:,} / {total:,} ({trainable/total:.2%})")
return model
# ========================
# 3. 示例:对一个Transformer模型应用LoRA
# ========================
class SimpleTransformer(nn.Module):
def __init__(self, vocab_size=1000, d_model=256, nhead=4,
num_layers=2, num_classes=10):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model, nhead=nhead,
dim_feedforward=512, batch_first=True
)
self.transformer = nn.TransformerEncoder(
encoder_layer, num_layers=num_layers
)
self.fc = nn.Linear(d_model, num_classes)
def forward(self, x):
x = self.embedding(x)
x = self.transformer(x)
x = x.mean(dim=1) # 全局平均池化
x = self.fc(x)
return x
# 创建模型并应用LoRA
model = SimpleTransformer()
print("=== 原始模型 ===")
total_params = sum(p.numel() for p in model.parameters())
print(f"总参数: {total_params:,}")
print("\n=== 应用LoRA ===")
model = apply_lora_to_model(model, rank=8, alpha=16)
# ========================
# 4. LoRA微调训练
# ========================
def train_with_lora(model, train_loader, num_epochs=10, lr=1e-3):
# 只优化LoRA参数
lora_params = [p for n, p in model.named_parameters() if p.requires_grad]
optimizer = torch.optim.AdamW(lora_params, lr=lr, weight_decay=0.01)
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
model.train()
total_loss = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f"Epoch [{epoch+1}/{num_epochs}] Loss: {avg_loss:.4f}")
5.4.2 使用Hugging Face PEFT库(推荐方案)
# ========================
# 使用PEFT库进行LoRA微调(最主流的方案)
# ========================
# pip install peft transformers datasets accelerate bitsandbytes
from transformers import (
AutoModelForSequenceClassification,
AutoTokenizer,
TrainingArguments,
Trainer,
)
from peft import (
LoraConfig,
get_peft_model,
TaskType,
PeftModel,
PeftConfig,
)
import torch
# ========================
# 1. 加载预训练模型
# ========================
model_name = "bert-base-uncased"
model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=2
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# ========================
# 2. 配置LoRA
# ========================
lora_config = LoraConfig(
task_type=TaskType.SEQ_CLS, # 任务类型:序列分类
r=8, # LoRA秩
lora_alpha=16, # 缩放因子
lora_dropout=0.1, # Dropout
target_modules=["query", "value"], # 应用LoRA的模块
bias="none", # 不训练bias
)
# ========================
# 3. 创建PEFT模型
# ========================
peft_model = get_peft_model(model, lora_config)
# 打印可训练参数信息
peft_model.print_trainable_parameters()
# 输出示例: trainable params: 296,450 || all params: 109,780,228 || trainable%: 0.2701
# ========================
# 4. 训练
# ========================
training_args = TrainingArguments(
output_dir="./lora_output",
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
learning_rate=2e-4, # LoRA通常用较大的学习率
warmup_ratio=0.06,
weight_decay=0.01,
logging_steps=50,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
fp16=True,
)
# 这里需要准备实际的train_dataset和eval_dataset
# trainer = Trainer(
# model=peft_model,
# args=training_args,
# train_dataset=train_dataset,
# eval_dataset=eval_dataset,
# tokenizer=tokenizer,
# )
# trainer.train()
# ========================
# 5. 保存LoRA权重(仅几MB!)
# ========================
peft_model.save_pretrained("./my_lora_weights")
# ========================
# 6. 加载LoRA权重推理
# ========================
# 重新加载基础模型
base_model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=2
)
# 加载LoRA权重
loaded_model = PeftModel.from_pretrained(
base_model, "./my_lora_weights"
)
loaded_model.eval()
# 推理
text = "This movie is really great!"
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
with torch.no_grad():
outputs = loaded_model(**inputs)
prediction = outputs.logits.argmax(dim=-1)
print(f"预测结果: {'正面' if prediction.item() == 1 else '负面'}")
5.4.3 LoRA微调LLaMA大语言模型(QLoRA)
# ========================
# QLoRA: 4-bit量化 + LoRA = 超低资源微调LLM
# ========================
# pip install transformers peft accelerate bitsandbytes datasets trl
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
from datasets import load_dataset
# ========================
# 1. 4-bit量化配置
# ========================
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NF4量化(QLoRA论文推荐)
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # 双重量化,进一步压缩
)
# ========================
# 2. 加载基座模型
# ========================
model_id = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.pad_token = tokenizer.eos_token
# 准备模型进行量化训练
model = prepare_model_for_kbit_training(model)
# ========================
# 3. LoRA配置
# ========================
lora_config = LoraConfig(
r=16, # 较大的rank适合LLM
lora_alpha=32, # alpha = 2 * r
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
target_modules=[ # 应用到所有注意力投影和FFN
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 输出: trainable params: 39,976,960 || all params: 6,778,765,312 || trainable%: 0.5898
# ========================
# 4. 准备训练数据
# ========================
dataset = load_dataset("tatsu-lab/alpaca", split="train[:1000]")
def format_prompt(example):
"""格式化为指令微调格式"""
if example["input"]:
return f"### Instruction:\n{example['instruction']}\n\n### Input:\n{example['input']}\n\n### Response:\n{example['output']}"
else:
return f"### Instruction:\n{example['instruction']}\n\n### Response:\n{example['output']}"
# ========================
# 5. 训练配置
# ========================
training_args = TrainingArguments(
output_dir="./qlora-llama2",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 有效batch_size = 4 * 4 = 16
learning_rate=2e-4,
weight_decay=0.01,
warmup_ratio=0.03,
lr_scheduler_type="cosine",
logging_steps=10,
save_strategy="epoch",
fp16=False,
bf16=True, # 使用BF16混合精度
optim="paged_adamw_8bit", # 8-bit优化器,节省显存
gradient_checkpointing=True, # 梯度检查点,用时间换空间
max_grad_norm=0.3,
report_to="none",
)
# ========================
# 6. 开始训练
# ========================
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
max_seq_length=512,
formatting_func=format_prompt,
)
# trainer.train()
# ========================
# 7. 保存和合并LoRA权重
# ========================
# 仅保存LoRA权重(几十MB)
# trainer.save_model("./qlora-llama2-lora")
# 合并LoRA权重到基座模型(可选)
# merged_model = model.merge_and_unload()
# merged_model.save_pretrained("./llama2-merged")
# ========================
# 8. 推理示例
# ========================
def generate_response(model, tokenizer, prompt, max_new_tokens=256):
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=0.7,
top_p=0.9,
do_sample=True,
)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
prompt = "### Instruction:\nExplain what is LoRA in simple terms.\n\n### Response:\n"
# response = generate_response(model, tokenizer, prompt)
# print(response)
5.4.4 LoRA权重合并与多任务热插拔
# ========================
# LoRA多任务管理:热插拔不同任务的LoRA权重
# ========================
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
# 基座模型只需加载一次
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
torch_dtype=torch.float16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# ========================
# 方式1:动态加载不同任务的LoRA
# ========================
def load_task_lora(base_model, lora_path):
"""加载特定任务的LoRA权重"""
model = PeftModel.from_pretrained(base_model, lora_path)
return model
# 任务A:代码生成
# model_code = load_task_lora(base_model, "./lora-code-generation")
# 任务B:医疗问答
# model_medical = load_task_lora(base_model, "./lora-medical-qa")
# 任务C:法律文书
# model_legal = load_task_lora(base_model, "./lora-legal-doc")
# ========================
# 方式2:合并LoRA权重(部署时推荐)
# ========================
# 将LoRA权重合并回基座模型,推理时无额外开销
# merged_model = model_code.merge_and_unload()
# merged_model.save_pretrained("./llama2-code-merged")
# ========================
# 方式3:多LoRA同时使用(LoRA集成)
# ========================
from peft import PeftModel
# 加载多个LoRA
model = PeftModel.from_pretrained(base_model, "./lora-task-a", adapter_name="task_a")
model.load_adapter("./lora-task-b", adapter_name="task_b")
# 切换活跃的LoRA
model.set_adapter("task_a") # 使用任务A的LoRA
# model.set_adapter("task_b") # 切换到任务B
# 加权混合两个LoRA
# model.add_weighted_adapter(
# adapters=["task_a", "task_b"],
# weights=[0.7, 0.3], # 70%任务A + 30%任务B
# adapter_name="mixed"
# )
5.5 LoRA变体与进阶
5.5.1 LoRA变体总结
| 变体 | 核心改进 | 适用场景 |
|---|---|---|
| LoRA | 基础版本,低秩分解 | 通用微调 |
| QLoRA | 4-bit量化 + LoRA | 超低资源微调LLM |
| LoRA+ | A和B使用不同学习率 | 提升收敛速度 |
| AdaLoRA | 自适应分配不同层的秩 | 自动优化rank分配 |
| DoRA | 分解权重为方向和大小 | 精度更接近全量微调 |
| rsLoRA | 缩放因子改为 1/√r | 大rank时更稳定 |
| GaLore | 梯度低秩投影 | 进一步节省优化器显存 |
5.5.2 DoRA简要介绍
# DoRA (Weight-Decomposed Low-Rank Adaptation)
# 核心思想:将权重分解为方向(direction)和大小(magnitude)两部分
# 标准LoRA: W' = W + BA
# DoRA: W' = m * (W + BA) / ||W + BA||_c
# 其中 m 是可学习的magnitude向量,||·||_c 是列范数
# DoRA在多个任务上比LoRA精度提升0.5~1%
5.5.3 LoRA超参数调优指南
┌─────────────────────────────────────────────────────────────┐
│ LoRA超参数调优指南 │
├──────────┬──────────────────────────────────────────────────┤
│ 参数 │ 建议 │
├──────────┼──────────────────────────────────────────────────┤
│ rank (r) │ • 小模型/简单任务: 4~8 │
│ │ • 大模型/复杂任务: 16~64 │
│ │ • 数据充足时可以用更大的rank │
├──────────┼──────────────────────────────────────────────────┤
│ alpha │ • 通常设为 rank 或 2*rank │
│ │ • alpha/rank 比值影响学习率的有效缩放 │
├──────────┼──────────────────────────────────────────────────┤
│ dropout │ • 通常 0.05~0.1 │
│ │ • 数据少时可适当增大 │
├──────────┼──────────────────────────────────────────────────┤
│ 目标模块 │ • 最少: query, value │
│ │ • 推荐: query, key, value, output │
│ │ • 最佳: 所有注意力+FFN层 │
├──────────┼──────────────────────────────────────────────────┤
│ 学习率 │ • 通常比全量微调大: 1e-4 ~ 3e-4 │
│ │ • 配合cosine或linear warmup调度器 │
├──────────┼──────────────────────────────────────────────────┤
│ batch size│ • 尽量大,配合梯度累积 │
│ │ • 有效batch_size建议16~64 │
└──────────┴──────────────────────────────────────────────────┘
六、四大压缩技术综合对比
6.1 技术维度对比
┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ 维度 │ 量化 │ 蒸馏 │ 剪枝 │ LoRA │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 压缩对象 │ 数值精度 │ 模型规模 │ 参数数量 │ 微调参数 │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 是否需要 │ PTQ:不需要 │ 需要 │ 需要 │ 需要 │
│ 重新训练 │ QAT:需要 │ │ (微调) │ (微调) │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 压缩比 │ 2~8× │ 可达10×+ │ 2~10× │ N/A │
│ │ (模型大小) │ (参数量) │ (参数量) │ (微调效率) │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 精度损失 │ 小(0.5~2%) │ 中(1~5%) │ 小~中 │ 极小(<1%) │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 实现复杂度 │ 低~中 │ 高 │ 中 │ 低 │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 额外数据需求 │ PTQ:少量 │ 需要(或 │ 少量 │ 下游任务 │
│ │ QAT:正常 │ 用原始数据) │ │ 微调数据 │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 推理加速 │ ✓ 显著 │ ✓ 显著 │ 结构化:✓ │ ✗ 无 │
│ │ │ │ 非结构化:△ │ (微调方法) │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 可组合性 │ 可与所有 │ 可与量化/ │ 可与量化/ │ 可与量化 │
│ │ 方法组合 │ 剪枝组合 │ 蒸馏组合 │ 组合(QLoRA) │
└──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
6.2 组合使用策略
推荐组合方案:
方案1(部署优化): 全量微调 → 量化(PTQ/INT8) → 部署
方案2(极致压缩): 蒸馏(大→小) → 剪枝 → 量化 → 部署
方案3(高效微调+部署): QLoRA微调 → 合并权重 → 量化 → 部署
方案4(持续迭代): LoRA微调 → 多任务热插拔 → 按需量化部署
七、工程实践建议与工具链
7.1 工具链推荐
| 工具 | 用途 | 链接 |
|---|---|---|
| PyTorch Quantization | PyTorch内置量化 | torch.quantization |
| bitsandbytes | LLM量化(INT8/INT4) | github.com/TimDettmers/bitsandbytes |
| AutoGPTQ | GPTQ量化 | github.com/AutoGPTQ/AutoGPTQ |
| llama.cpp | CPU端LLM推理(GGUF格式) | github.com/ggerganov/llama.cpp |
| PEFT | LoRA/AdaLoRA等PEFT方法 | github.com/huggingface/peft |
| Transformers | Hugging Face模型生态 | github.com/huggingface/transformers |
| vLLM | 高性能LLM推理服务 | github.com/vllm-project/vllm |
| TensorRT | NVIDIA推理优化引擎 | developer.nvidia.com/tensorrt |
| ONNX Runtime | 跨平台推理优化 | onnxruntime.ai |
| Intel Neural Compressor | Intel平台量化工具 | github.com/intel/neural-compressor |
7.2 实践建议
┌─────────────────────────────────────────────────────────────┐
│ 模型压缩实践建议 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 先明确约束条件 │
│ - 目标延迟是多少?(如 <100ms) │
│ - 目标模型大小?(如 <100MB) │
│ - 可接受的精度损失?(如 <1%) │
│ - 目标硬件是什么?(GPU/CPU/移动端) │
│ │
│ 2. 选择合适的压缩策略 │
│ - 有训练数据 → 蒸馏/QAT/LoRA │
│ - 无训练数据 → PTQ/动态量化 │
│ - 需要实际加速 → 量化/结构化剪枝 │
│ - 需要多任务 → LoRA │
│ │
│ 3. 渐进式压缩 │
│ - 先尝试最简单的方法(如INT8 PTQ) │
│ - 精度不够再尝试更复杂的方法(如QAT/蒸馏) │
│ - 记录每一步的精度和性能指标 │
│ │
│ 4. 充分验证 │
│ - 在代表性测试集上评估 │
│ - 检查边缘case的精度 │
│ - 测量实际端到端延迟,而非理论FLOPs │
│ │
│ 5. 版本管理 │
│ - 保存原始FP32模型作为基线 │
│ - 记录每个压缩版本的超参数和精度 │
│ - LoRA权重单独存储,方便回退 │
│ │
└─────────────────────────────────────────────────────────────┘
7.3 不同场景的推荐方案
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| LLM本地部署(消费级GPU) | QLoRA微调 + INT4量化 | 显存需求最低 |
| 服务端高吞吐推理 | 全量微调 + INT8 PTQ | 精度和速度平衡 |
| 移动端部署 | 蒸馏到小模型 + INT8量化 | 极致压缩 |
| 多任务快速迭代 | LoRA微调 + 基座共享 | 存储和切换效率最高 |
| 精度敏感场景 | QAT量化 | 精度损失最小 |
| 研究/原型验证 | 动态量化 | 最快速验证 |
八、总结与展望
8.1 核心要点回顾
模型压缩 = 精度与效率的权衡艺术
量化 → 改变"数字的精度" → FP32 → INT8/INT4
蒸馏 → 改变"模型的大小" → 大模型 → 小模型
剪枝 → 改变"参数的数量" → 稠密 → 稀疏
LoRA → 改变"微调的方式" → 全量更新 → 低秩更新
8.2 前沿趋势
- 1.更激进的量化:1-bit LLM(BitNet)、2-bit量化研究持续推进
- 2.蒸馏+MoE:将大MoE模型蒸馏为稠密小模型
- 3.自适应压缩:不同层使用不同的压缩比(如敏感层少压缩)
- 4.硬件-算法协同设计:针对特定硬件的最优压缩方案
- 5.LoRA家族持续扩展:DoRA、GaLore、LoRA+等变体不断涌现
- 6.端侧大模型:手机端运行7B甚至更大参数的模型成为现实
8.3 学习路径建议
入门路径:
PyTorch量化API → 简单模型蒸馏 → LoRA微调 → 实际项目实践
进阶路径:
QAT深入理解 → 蒸馏策略优化 → 结构化剪枝 → QLoRA/DoRA → 压缩组合策略
研究方向:
低比特量化理论 → 蒸馏的理论基础 → 剪枝的彩票假说 → PEFT方法创新
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)