第 29 / 30 课。 前置:上一课。本篇的运行范围以实验说明和验证记录为准,构造样例不代表真实任务精度或板端性能。
三种方法改变不同的东西
量化减少数值表示精度;蒸馏用教师信号指导学生;结构化剪枝改变网络宽度等结构。它们可以组合,但应分别建立基线,否则很难判断精度变化来自哪里。
本课的量化部分手工模拟对称 int8 权重,逐输出通道计算 scale,并反量化比较误差;不模拟激活量化,也不宣称它等价于目标 NPU 的完整量化方案。校准样本、量化轴和舍入规则需与实际工具对齐。
蒸馏怎样传递软信息
教师输出经温度 T 调整后作为软目标,学生使用 KL 损失学习分布,同时保留真实标签的交叉熵。常见缩放使用 T² 补偿梯度尺度。本课先在构造二分类数据上训练较宽教师,再训练较窄学生,教师保持 eval 并关闭梯度。
学生对教师的拟合下降不自动表示真实精度提升;教师也可能犯错。要与相同学生结构、相同训练预算、只用硬标签的基线比较,并使用独立验证数据。
剪枝为何需要重建后续层
把权重设为零通常不改变稠密执行形状。真实裁掉中间通道时,需要同步裁掉当前层输出和下一层输入,以及相关 BN、残差和分组约束。本课用两层线性网络展示重建,并证明其输出等于“被裁通道显式置零”的参考网络。
这种对照只证明裁剪索引正确,不证明与原始未裁网络相同。任务精度可能需要微调恢复。
练习与验收
给一个输出通道放入离群权重,比较逐张量与逐通道误差;改变蒸馏温度,记录硬标签损失与 KL;裁掉更大比例通道,比较学生或剪枝后的验证质量。
最终报告至少包含原始、量化、结构改变三个明确版本的文件哈希、质量、内存和端到端延迟。只有真实部署运行库使用了低精度或更窄结构,才可能获得对应资源收益。
配套实验与运行记录
实验:量化、蒸馏与剪枝的实际取舍
查看完整代码:main.py
"""运行逐通道量化误差、教师学生训练和通道裁剪对照,理解三种方法的不同作用。"""
import copy
import torch
from torch import nn
import torch.nn.functional as F
def main():
torch.manual_seed(7)
torch.set_num_threads(2)
w = torch.randn(4, 8)
scale = w.abs().amax(1, keepdim=True).clamp_min(1e-8) / 127
q = torch.round(w / scale).clamp(-127, 127)
restored = q * scale
assert bool(((w - restored).abs() <= scale / 2 + 1e-6).all())
print("per-channel simulated weight max error", float((w - restored).abs().max()))
x = torch.randn(128, 4)
y = (x[:, 0] + x[:, 1] > 0).long()
teacher = nn.Sequential(nn.Linear(4, 16), nn.ReLU(), nn.Linear(16, 2))
opt = torch.optim.Adam(teacher.parameters(), lr=0.03)
for _ in range(60):
opt.zero_grad()
loss = F.cross_entropy(teacher(x), y)
loss.backward()
opt.step()
teacher.eval()
with torch.no_grad():
soft = (teacher(x) / 2).softmax(1)
student = nn.Sequential(nn.Linear(4, 4), nn.ReLU(), nn.Linear(4, 2))
baseline = copy.deepcopy(student)
opt = torch.optim.Adam(student.parameters(), lr=0.03)
plain = torch.optim.Adam(baseline.parameters(), lr=0.03)
initial = float(F.cross_entropy(student(x), y).detach())
for _ in range(80):
logits = student(x)
loss = 0.5 * F.cross_entropy(logits, y) + 0.5 * 4 * F.kl_div(
(logits / 2).log_softmax(1), soft, reduction="batchmean"
)
opt.zero_grad()
loss.backward()
opt.step()
plain.zero_grad()
F.cross_entropy(baseline(x), y).backward()
plain.step()
final = float(F.cross_entropy(student(x), y).detach())
assert final < initial
test = torch.randn(256, 4)
truth = (test[:, 0] + test[:, 1] > 0).long()
with torch.no_grad():
print(
"synthetic held-out accuracy teacher/student/hard-only",
*[
float((m(test).argmax(1) == truth).float().mean())
for m in [teacher, student, baseline]
]
)
first = nn.Linear(4, 6)
last = nn.Linear(6, 2)
keep = torch.tensor([0, 2, 5])
small_first = nn.Linear(4, 3)
small_last = nn.Linear(3, 2)
with torch.no_grad():
small_first.weight.copy_(first.weight[keep])
small_first.bias.copy_(first.bias[keep])
small_last.weight.copy_(last.weight[:, keep])
small_last.bias.copy_(last.bias)
hidden = torch.relu(first(x))
mask = torch.zeros(6)
mask[keep] = 1
reference = last(hidden * mask)
actual = small_last(torch.relu(small_first(x)))
torch.testing.assert_close(actual, reference, atol=1e-6, rtol=1e-5)
print(
"distillation train CE",
initial,
final,
"channel-pruning index equivalence passed",
)
if __name__ == "__main__":
main()
在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。
cd 'content/posts/AI-深度学习/嵌入式视觉模型进阶/29-量化、蒸馏与剪枝的实际取舍/code' && python3 main.py
在本篇代码目录执行 python3 main.py。依赖安装、可选入口见包内 README.md;实际运行结果见 verification.md。下载包包含本课使用的辅助模块与样例。
参考与进一步阅读
原始论文或官方文档用于核对结构、接口和适用范围。本篇代码是围绕教学目标编写的最小实验;除明确注明的完整入口外,不视为对应论文模型的完整复现。