☰
Model-Optimizer:从优化器选型到模型压缩的工程实践
2026/11/4 15:05:02 网站建设 项目流程

如果你也在反复调深度学习模型,大概率撞到过这两类问题:一是训练时损失卡住不动,换了个优化器也没太大起色;二是模型总算训出来了,但推理延迟太高,线上根本扛不住。我今年完整做了一个内部项目,代号就叫Model-Optimizer,初衷很朴素——把训练阶段的优化器(optimizer)选型和部署阶段的模型优化(model optimization)动作,统一收进一套可复用的流水线。折腾下来有个很深的体会:这两个名字里都带"optimize"的方向,平时常被当成两件事,但真正常踩的坑其实是相通的,要么是超参数没理解到位,要么是优化动作缺回退机制。这篇文章就围绕Model-Optimizer讲清楚三件事:优化器怎么选、模型怎么压、以及两者怎么在一条工程流水线里协作。适合正在做模型训练、推理加速、模型上线的工程师参考,也适合刚入门想弄清楚训练和部署全流程的同学。

1. Model-Optimizer到底是什么:先分清两个"优化"

很多人第一次看到Model-Optimizer这个名字,会默认它只是一个优化器封装库,比如把SGD、Adam、AdamW换个接口统一调用。但在我这个项目里,它覆盖的范围要宽得多。训练时的优化器解决的,是"模型怎么学会"的问题;部署时的模型优化解决的,是"学会之后怎么跑得快"的问题。两者差着训练和推理两个阶段,但放在一个体系里统一管理,收益非常大。

1.1 训练优化器:让模型学会

先回到最基础的问题。神经网络训练的本质,是不断让损失函数的值变小。损失函数告诉我们当前模型错得有多离谱,而优化器负责根据这个信号去调整模型里的成千上万个参数。没有优化器,模型参数就一直停在初始值,训练也就是一句空话。

优化器做的事情,可以简化成一句参数更新公式:

[ \theta_{t+1} = \theta_t - \eta \cdot update_direction_t ]

其中 (\theta) 是模型参数,(\eta) 是学习率,(update_direction) 则是优化器根据梯度计算出来的更新方向。不同优化器的核心差异,基本都体现在"怎么算这个更新方向"上。

理解优化器,最好的类比是下山。损失函数是一座山,模型参数是你在山上的坐标,你的目标是走到山谷最低点。梯度告诉你当前位置哪边坡度最陡,学习率决定你每一步迈多大。SGD就是老老实实沿着最陡方向走;动量(Momentum)相当于给下山加了个惯性,走快了不容易停在小坑里;Adagrad、RMSProp则相当于根据走过的路况自动调整步幅,平坦的地方快走,坑洼的地方慢走;Adam把"惯性"和"自适应步幅"结合起来,所以大部分场景下开箱即用。

1.2 模型优化:让模型跑得更快

到了部署阶段,我们关心的不再是训练损失,而是推理延迟、吞吐量、显存占用、模型文件大小这些指标。模型优化,指的是在尽量少损失精度的前提下,让模型变得更"轻"。

常用手段包括剪枝、量化、知识蒸馏、算子融合。剪枝是把模型里冗余的权重或通道裁掉;量化是把FP32的权重和激活用INT8甚至更低精度表示;蒸馏是让一个大模型"教"一个小模型;算子融合是把多个计算合并成一个,减少访存和内核启动开销。我后面会逐个展开。

有一种说法是"训练优化器管模型精度,模型优化管模型效率",这个说法大方向对,但不够完整。因为量化、剪枝之后模型精度掉了,往往需要重新微调,而微调阶段又用到了优化器。所以这两个"优化"根本割不开,Model-Optimizer的真正价值就是把它们串成一条链路。

1.3 为什么把两个优化放进同一套体系

在实际团队里,算法工程师负责训练,部署工程师负责优化,这很容易出现脱节:算法这边训出了一个精度很高的模型,但结构和数值分布对量化极不友好;部署那边为了满足性能指标,硬上量化,结果精度掉得没法看。两边互相认为是对方的锅。

我搭Model-Optimizer的时候,核心目标就是打破这种割裂。在训练阶段就把部署约束考虑进去,比如记录每一层权重和激活值的分布、埋好BN层信息,为后续量化做准备;在优化阶段又把训练阶段的超参经验复用过来,比如用优化器配置做微调来恢复压缩损失。实践证明,不需要动大架构,只需要把流程串起来,模型上线时间能缩短一半以上。

2. 训练优化器选型:从SGD到AdamW的实战对比

训练阶段的优化器选型,是我见过争议最多的话题。有人坚信SGD天下无敌,有人无脑Adam,也有人说只要数据量够大、调参够狠,什么优化器差别都不大。说实话,这些说法各有适用场景。我这边的经验是:先理解每个优化器的行为特征,再根据任务类型做选择。

2.1 SGD+Momentum还是Adam,主要看任务和数据

SGD加上Momentum,在一大类CV任务上仍然是很强的基线。它的优点在于:更新过程相对平稳,模型更容易收敛到泛化性能好的平缓极小值区域,并且几乎不引入额外内存开销。缺点是对学习率非常敏感,需要比较精细的学习率调度,训练前期如果步长太大,容易在损失曲面中震荡。

Adam类优化器则引入了二阶矩估计,给每个参数自适应的学习率,天然适合稀疏梯度和非平稳目标。它的收敛速度快,对学习率的敏感度低,但早期版本有一个隐患:训练后期可能出现学习率被分母的指数滑动平均压得过小,导致模型收敛不足,或者泛化性不如SGD。AdamW的改进大家后面再细说。

我自己的选型逻辑大致是这样:

维度SGD + MomentumAdam / AdamW
收敛速度相对较慢相对较快
学习率敏感度敏感,需要调度不太敏感,但需要合理初值
泛化能力普遍较强调好也不差,AdamW更好
内存开销低(动量)高(一阶+二阶矩)
适合场景小数据集、CV、需要精细控制大模型、NLP、多任务、稀疏场景
推荐默认lr0.01~0.11e-4~3e-4

如果模型不大、数据量在几万级别,我会先跑SGD+Momentum,配合cosine学习率调度做基线。如果模型参数量千万以上,或者涉及Transformer类结构,直接上AdamW,省心很多。

2.2 AdamW里weight decay的正确姿势

很多同学搞混L2正则和weight decay,因为PyTorch旧的接口里,SGD的weight_decay参数实现的其实就是L2正则——把权重的平方和加到损失里,梯度里自然多了一项。

Adam出现后问题来了:Adam在更新梯度前会做偏置校正和二阶矩归一化,直接把L2正则加在梯度里,再除以二阶矩,会让weight decay的力度被动态放大或缩小,效果变得不稳定。AdamW的贡献很直接:把weight decay从梯度计算中拆出来,在参数真正更新的时候单独做一次衰减,相当于"先按自适应学习率更新梯度,再统一把权重往0方向拉一点"。实际效果是正则项行为更稳定,泛化性更好,训练也更容易收敛。

用PyTorch写出来就是这样:

optimizer = torch.optim.AdamW( model.parameters(), lr=1e-4, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01, )

这里weight_decay=0.01是我在大模型微调里常用的初始值。如果数据集小、容易过拟合,可以提到0.05;如果数据量很大或者模型正则已经够强,可以降到0.001。关键是要明确一点:AdamW里的weight_decay是"真·权重衰减",不是L2正则,不要再往loss里手写torch.norm(w, 2)那一套了,重复加会导致衰减过度。

2.3 学习率调度:warmup和cosine一起用

优化器选好只是第一步,真正让训练进入稳定轨道的是学习率调度。我在项目里长期使用的组合是:线性warmup + cosine退火。

warmup一般持续到总训练步数的5%到10%。比如总共训练100个epoch,warmup 5到10个epoch。前50步先不说完美,但一口气从0升到目标学习率,有助于稳定分布; 不然模型参数在初期剧烈变化,容易出现损失冲高甚至NaN。 从0线性升到目标lr,之后再按cosine曲线逐步降到接近0。

代码可以这样写:

def build_scheduler(optimizer, total_steps, warmup_steps): def lr_lambda(step): if step < warmup_steps: # 线性升温到1.0 return float(step + 1) / float(warmup_steps) # cosine退火,最小降到峰值的1% progress = float(step - warmup_steps) / float(total_steps - warmup_steps) return 0.01 + 0.99 * 0.5 * (1.0 + math.cos(math.pi * progress)) return torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)

这套组合在实际项目里表现很稳。尤其从预训练模型做微调时,warmup能防止开局就破坏预训练学到的参数分布;cosine退火则让模型在后期有足够细的步长去落到更好的极小值。

2.4 我给不同任务开的优化器配置

我把几个常用配置直接列成了一张表,新项目基本从这里面挑,改改动就行:

任务类型优化器lrbeta/动量weight_decay调度器
小数据分类SGD0.050.95e-4cosine
中大型CNNSGD/AdamW0.01 / 1e-40.9 / (0.9,0.999)1e-4warmup+cosine
Transformer微调AdamW1e-4~3e-4(0.9,0.999)0.01~0.05warmup+cosine
目标检测AdamW1e-4(0.9,0.999)1e-4warmup+cosine

注意这里的lr差别很大,不是因为代码写错了,而是因为任务结构差异。SGD能扛住0.05的大学习率,AdamW通常要用1e-4量级。我第一次用AdamW直接把lr写成SGD的0.01,结果训练直接飞了,梯度爆炸。后来才意识到,Adam的自适应步长会把有效学习率放大很多,初始lr必须保守。

3. 模型压缩的四种核心手段与应用场景

训练阶段稳定之后,模型压缩就要上场。这一节我会把剪枝、量化、知识蒸馏、算子融合逐个拆开,讲清楚原理、适合场景,还有实操里的坑。

3.1 剪枝:把冗余参数和通道裁掉

神经网络的参数数量远大于解决问题所需的最小参数量,这几乎是公认的。剪枝的思路就是找到那些对输出影响很小的参数,把它们置零甚至从结构上删除,从而减少计算量。

剪枝分成两类:非结构化剪枝和结构化剪枝。非结构化剪枝是细粒度地把权重矩阵里某些元素置零,模型变得稀疏,但在通用硬件上很难获得真正加速,除非配合稀疏计算库。结构化剪枝则是整行、整列、整个channel地裁掉,比如丢弃某个卷积的某个输入通道,这样卷积计算量能实打实地减少,不依赖特殊硬件。

实操中我优先推荐结构化剪枝,典型流程是:

  1. 对训练好的模型,按某种重要性准则评估每个channel。
  2. 把不重要的channel剪掉,更新模型结构。
  3. 用训练集对剪枝后的模型做短周期微调,恢复精度。

这里最容易被忽略的是BN层。BN层有缩放系数gamma,很多剪枝方法会直接用gamma的L1/L2范数作为channel重要性分数。实现时要注意,剪完一个channel,后续所有依赖它的层都要同步改,包括卷积的输入维度、BN层、以及残差连接。手写起来容易崩,我的建议是先用现成框架(比如torch.nn.utils.prune)跑通流程,再决定要不要深入定制。

3.2 量化:降低数值精度换取速度

量化是另一种完全不同的思路。它不删参数,而把FP32精度降到INT8甚至更低。为什么INT8能加速?因为低精度乘法更快,而且单位时间内能搬进计算单元的数据更多,尤其在支持INT8算力的芯片上,推理吞吐量可以翻几倍。

量化分为训练后量化(PTQ)和量化感知训练(QAT)。PTQ是模型训练好之后,直接统计激活值分布,把缩放因子算出来,简单粗暴。优点是快,缺点是精度损失通常更大,尤其对激活值分布不规律的模型,掉点可能很严重。QAT则在训练中就模拟量化的舍入误差,让模型自己适应低精度表示,精度好很多,但需要重新训练,成本高。

实际操作里我的顺序是:先做PTQ,评估精度损失。如果损失在可接受范围内,直接上。如果掉了2个点以上,再考虑QAT,并对敏感层做混合精度处理——把所有层统一量化成INT8不一定最优,对敏感层保留FP16,往往能在速度和精度之间找到更好的平衡。

3.3 知识蒸馏:让大模型给"小模型"带路

剪枝和量化一般用于已经训好的大模型,而知识蒸馏更像是在训练一个天生更小的模型。做法是先有一个性能强大的大模型(Teacher),让小模型(Student)同时学习真实标签和大模型的输出分布。

为什么要学大模型的输出分布,而不直接学标签?因为大模型的softmax输出里藏着类别之间的相似度信息。比如一张图是猫,大模型输出猫的概率0.7,狗0.25,狐狸0.05,这个分布告诉学生模型:狗和猫是相近的,狐狸相对远一些。而原始标签只告诉它"这是猫",信息量少很多。

蒸馏时还会引入一个温度参数T:

[ q_i = \frac{exp(z_i / T)}{\sum_j exp(z_j / T)} ]

T越大,分布越平滑,把类间关系暴露得更充分。我一般用T=3~5做训练,用T=1做验证。学生模型的最终损失是硬标签交叉熵和软标签KL散度的加权和,权重取决于项目需要,常见取0.5。

这个方案特别适合要把模型换小的场景。比如线上要求模型跑进5ms,大模型怎么压缩都做不到,那就直接用蒸馏训一个MobileNet级别的学生模型,效果往往比直接训练小模型好很多。

3.4 算子融合与推理引擎:最后一公里的加速

剪枝、量化之后,模型结构还在。推理的时候还有一类开销来自层与层之间的切换:每次计算完一个算子,要把结果写回内存,下一个算子再从内存读出来。访存往往比计算还贵。算子融合的思路就是把多个算子合并成一个,减少中间结果的读写。

最典型的例子是Conv+BN融合。推理阶段BN其实是一个确定的线性变换,如果模型已经训练完成,完全可以把BN的缩放和平移统一合并进卷积的权重和偏置里,推理时就少跑一个算子,还省掉了一次中间内存读写。我实测过一个ResNet结构的模型,单纯做Conv+BN融合,CPU上能带来15%以上的提速。

这类优化在推理引擎里通常已经内置,比如ONNX Runtime、TensorRT等。所以我做这套Model-Optimizer时,并不会手写融合逻辑,而是把导出的模型交给推理引擎去自动优化。要点是导出模型时要保持算子的标准形态,不要随便把多个操作合成自定义op,否则引擎的自动融合反而失效。

4. Model-Optimizer流水线的完整落地实操

前面讲的是单点技术,这一节我把整套流水线的工程实现拉出来,给出可以直接参考的项目结构和代码。

4.1 项目结构设计

Model-Optimizer的目录我建议这样拆:

model_optimizer/ ├── configs/ │ ├── train_cnn.yaml │ ├── finetune_bert.yaml │ └── compress_quantize.yaml ├── optimizer/ │ ├── build_optimizer.py │ ├── build_scheduler.py │ └── losses.py ├── compress/ │ ├── prune.py │ ├── quantize.py │ ├── distill.py │ └── fuse.py ├── evaluate/ │ ├── latency.py │ └── accuracy.py └── pipeline.py

这种分层方式的核心原则是:训练和压缩互相解耦,但通过统一的config对接。训练阶段把模型权重和统计信息保存下来;压缩阶段读取同一个config,知道每一层的名字、类型、BN参数,从而做出正确的压缩和恢复动作。我最初是各写各的,后来发现手动同步配置太容易出错,才改成这种统一结构。

4.2 训练阶段:一套配置搞定优化器和调度器

训练模块里,我会把优化器和调度器从模型代码中完全剥离,统一用config驱动。下面是一个简化的PyTorch代码骨架:

import math import torch def build_optimizer(cfg, model): if cfg.optimizer == "sgd": optimizer = torch.optim.SGD( model.parameters(), lr=cfg.lr, momentum=cfg.momentum, weight_decay=cfg.weight_decay, nesterov=cfg.get("nesterov", False), ) elif cfg.optimizer == "adamw": optimizer = torch.optim.AdamW( model.parameters(), lr=cfg.lr, betas=tuple(cfg.betas), eps=cfg.eps, weight_decay=cfg.weight_decay, ) else: raise ValueError(f"unsupported optimizer: {cfg.optimizer}") return optimizer def build_scheduler(cfg, optimizer, total_steps): warmup_steps = int(total_steps * cfg.warmup_ratio) if cfg.scheduler == "warmup_cosine": def lr_lambda(step): if step < warmup_steps: return float(step + 1) / float(warmup_steps) progress = float(step - warmup_steps) / max(1.0, float(total_steps - warmup_steps)) return 0.01 + 0.99 * 0.5 * (1.0 + math.cos(math.pi * progress)) return torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda) elif cfg.scheduler == "step": return torch.optim.lr_scheduler.StepLR(optimizer, step_size=cfg.step_size, gamma=cfg.gamma) else: raise ValueError(f"unsupported scheduler: {cfg.scheduler}")

这段代码看着不起眼,但它把项目里的一个痛点解决了:所有同事都在用同一套优化器配置语言,不会再出现"我用Adam却没写betas"这种细节事故。

训练时还有一个我强烈建议的改动:在训练循环里周期性记录梯度范数。不要等出了问题再翻log,把这个指标作为训练日志的标准字段。

total_norm = 0.0 for p in model.parameters(): if p.grad is not None: param_norm = p.grad.detach().data.norm(2) total_norm += param_norm.item() ** 2 total_norm = total_norm ** 0.5 logger.info(f"step {step}, grad_norm {total_norm:.4f}, lr {lr:.6f}")

一旦梯度范数出现指数级上涨,基本可以预判下一步就要loss爆炸了,提前减小lr再训练,比炸了再救要轻松得多。

4.3 压缩阶段:剪枝、量化和评估的一体化流程

压缩阶段,我同样把它串成一个pipeline。先看剪枝部分的代码:

import torch.nn.utils.prune as prune def apply_prune(model, prune_amount=0.2): for name, module in model.named_modules(): if isinstance(module, torch.nn.Conv2d): prune.l1_unstructured(module, name="weight", amount=prune_amount) prune.remove(module, "weight") # 去掉mask,真正重写权重 return model

这里一个容易踩的坑是:prune.l1_unstructured会在模块上叠加一个weight_mask,如果不调用prune.remove,模型里会一直存在一个额外的mask张量,导出推理时一是占内存,二是可能影响算子融合。所以正式做部署模型压缩时,一定要在剪完并finetune之后把mask固化到权重里再导出。

量化部分用PyTorch的官方接口做一个示例:

backend = "fbgemm" model.eval() model.qconfig = torch.quantization.get_default_qconfig(backend) model_fused = torch.quantization.fuse_modules(model, [["conv", "bn", "relu"]]) model_prepared = torch.quantization.prepare(model_fused, inplace=False) # 用一小部分训练集统计激活分布 calibrate(model_prepared, calib_loader, num_batches=200) model_quantized = torch.quantization.convert(model_prepared, inplace=False)

注意这里的顺序是固定的:先融合,再prepared,再用校准集统计分布,最后convert。校准集非常关键,不能随便拿几张图就完事,最好覆盖所有类别的典型样本,否则激活分布偏了,量化点掉得莫名其妙。

评估和回退逻辑同样重要。我的做法是评估精度和延迟两套指标,所有压完的模型都先和baseline对比:

def evaluate_and_compare(model, baseline_metrics, acceptable_loss=0.02): metrics = evaluate(model) acc_loss = baseline_metrics["accuracy"] - metrics["accuracy"] speedup = metrics["latency"] / baseline_metrics["latency"] if acc_loss <= acceptable_loss: return {"status": "pass", "model": model, "speedup": speedup} else: return {"status": "reject", "reason": f"accuracy drop {acc_loss:.4f}"}

如果超过精度损失预算,自动回退到上一版模型,或者报警让工程师介入。这个机制价值巨大——它会阻止你为了追求速度而无脑量化,也避免"手动试模型压完没对比"这种低级失误。因为模型上线最怕的不是精度掉一点,而是掉了很多还不知道。

4.4 用数据说话:一份真实的压缩效果记录

在某次图像分类项目上,我用这套flow实际跑出了下面这份数据:

版本模型大小推理耗时(单图CPU)准确率
原始FP32120 MB25.3 ms91.2%
Conv+BN融合120 MB21.7 ms91.2%
结构化剪枝30%82 MB16.5 ms90.8%
剪枝 + INT8量化21 MB7.2 ms90.1%
QAT恢复21 MB7.2 ms90.7%

这份数据很典型:融合不损失精度,但提速有限;剪枝和量化组合起来,模型体积缩小了近6倍,推理耗时降低了近72%,最终精度只掉了0.5个点。实际项目完全在可接受范围内。这个案例也说明,单一优化动作往往不够,组合拳才是常态。

5. 常见问题与排查技巧实录

写到最后,把我在Model-Optimizer项目里踩过、也帮别人排查过的典型问题整理成一份速查表。这节全部来自真实过程,不含任何理想化的假设。

5.1 损失不下降,训练看起来"卡住"了

这是最常见的问题,但"卡住"的原因差异很大。先看学习率,如果用的AdamW,lr在1e-4量级却完全没动静,先确认有没有做warmup、是不是warmup步数太长占了大半训练。我见过有人设置warmup_ratio=0.3,前30%步数都在升温,损失自然迟迟不掉。

再看优化器参数有没有在多个地方被重置。比如有的代码习惯循环里手动optimizer.zero_grad(),有的又调了scheduler.step(),版本新老不一,很容易出现学习率被意外调为0。排查方法很简单:把每个step的lr和grad_norm打印出来,如果lr曲线不对,问题一目了然。

最后才是考虑模型结构问题。比如自定义层没有有效梯度,或者损失函数写错。这种时候可以先用小数据跑若干步做梯度检查,确认从输入到loss每层的梯度都不是"非零且合理"以外的状态。

5.2 梯度爆炸或NaN

梯度爆炸的典型特征是grad_norm从几十跳到上万,然后loss直接变NaN。原因一般是lr太大、模型结构深、或者优化器实现有误。我推荐的应急处理是先用梯度裁剪:

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

裁剪机制是:如果参数总梯度范数超过max_norm,就整体缩放到max_norm。注意它是把梯度向量整体缩放,不是简单截断单个值,所以能保留所有参数的更新方向,只限制总步长。加上这个之后,梯度爆炸导致的NaN基本能挡住。

如果问题依然存在,就要查是否有数值下溢。比如用了fp16混合精度,loss太小导致梯度被清零,可以先调大loss scale或者用更稳的AMP实现。NaN还有一种隐蔽来源:学习率调度到后期退火到极小值,分母或中间计算出现除零,这类问题要靠逐层检查浮点数值来定位。

5.3 量化后精度掉点严重

量化掉点,第一个想到的应该是校准集不合理。如果校准图像只有单一类别,激活分布就是偏的,量化缩放因子自然不准。我踩过一次很惨的坑:用100张验证集做校准,验证集是顺序排列的,前几十张全是同一类,量化后精度掉了9个点。换成打乱后的均衡子集之后,掉点立刻收敛到1个点左右。

如果校准集没问题,就考虑敏感层混合精度。不需要所有层都INT8,某些对量化极度敏感的层,比如注意力里的Softmax后激活、检测头的最后一层,保留FP16,通常能把掉的点补回来。

5.4 剪枝后模型不明显变小、推理也没变快

这个问题通常源于剪成了无效结构。用torch.nn.utils.prune的mask剪枝,模型结构并没有真正改变,推理引擎看到的还是原来的稠密矩阵,自然不加速。正确做法是剪枝后重构模型:拿到保留下来的channel索引,重新创建一个小尺寸网络,再把权重拷贝进去。这一步在框架里没有现成接口,需要根据你的模型结构手写映射。

另外要检查剪枝和微调的节奏。一次性剪30%再微调,和每次剪5%分多轮做,效果差别很大。前者容易损伤精度,后者更稳。我一般把总剪枝比例分成6~8轮,每轮剪完微调几个epoch,效果明显更好。

下面这张速查表可以直接存下来:

问题现象常见原因处理优先级
训练损失不下降warmup过长、lr被置零、梯度消失先打印lr和grad_norm
梯度爆炸/NaNlr过大、梯度未裁剪、fp16下溢先加梯度裁剪,再降lr
量化掉点严重校准集不均衡、敏感层被强压INT8先修校准集,再混合精度
剪枝后不加速只加了mask未重构结构重构模型,导出真正稀疏/小模型
蒸馏学生学不到东西T温度过低、软标签权重太小适当升高T,平衡两类loss

5.5 蒸馏训练的一些私人心得

最后一个花絮,分享一个关于知识蒸馏的细节。很多人调参时只关注温度T和loss权重,忽略了大模型本身需要先做置信度校准。我遇到过一个大模型在训练集上准确率很高,但softmax输出过度自信,几乎都是0.99,学生从这个分布里学不到"猫和狗像不像"这种信息。解决办法是在蒸馏前对大模型做一点温度缩放,或者用验证集统计它的真实置信度,把输出分布稍微压平再当teacher。

这大概就是Model-Optimizer项目里最值得沉淀的一条经验:任何一个优化动作,不管听起来多成熟,都要先观察它到底改变了什么,再决定怎么用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询