ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

模型优化器完全指南:从AdamW调参到混合精度与推理优化

模型优化器完全指南:从AdamW调参到混合精度与推理优化 我先说个事。去年我给一个智能客服项目调模型训练 loss 卡在 0.7 上下死活下不去试了各种网络结构调整都没用。后来排查到根上是优化器参数组合踩了坑——Adam 加 weight decay 的写法不对把 L2 正则加在了不该加的地方。那一下午的浪费让我下定决心要写一篇把“Model-Optimizer”这件事彻底讲透的文章。这篇内容适合真正在做模型训练和部署的人——不管你在搞 CV、NLP 还是多模态也不管你用的是 PyTorch 还是 TensorFlow只要你在调参、踩坑、折腾模型效果这篇就能帮你少走点弯路。我会从优化器选型讲起一直聊到推理阶段的量化剪枝把训练和部署两个阶段的“优化”策略都铺开讲最后附上我亲身踩过的坑和排查思路。1. 优化器的本质你在“调”的到底是什么1.1 一次参数更新背后发生了什么很多新手把优化器当成一个“黑盒”写一行optimizer Adam(model.parameters())就完事了这会导致很多问题排查不了。其实优化器做的工作可以拆成四步计算梯度、处理梯度、计算更新量、更新参数。其中第二步和第三步是不同优化器的分水岭。以最基础的 SGD 为例它的更新就是w w - lr * grad。这里面没有对梯度做任何“修饰”所以 SGD 的行为完全取决于学习率和梯度本身的形状。而 Adam 这样的自适应方法会对梯度做一阶矩和二阶矩的估计——一阶矩相当于梯度的“平滑版”二阶矩相当于梯度的“振幅估计”。每一步更新量实际上是“平滑梯度除以振幅开根号”再乘上学习率。这个除法带来了一个很关键的后果Adam 对每个参数维度的更新量会做归一化让所有维度的更新步长大致在一个量级。这让 Adam 在稀疏梯度、非平稳目标上表现很好但同时也会带来泛化性偏弱的问题——这是后面所有“Adam 调参技巧”的出发点。——— 另一个容易忽略的细节是学习率。SGD 的学习率直接控制步长大小Adam 里也有学习率但它对学习率的敏感度其实低于 SGD。SGD 的学习率可以从 0.1 降到 0.001 跨度调Adam 通常在 3e-4 到 1e-2 之间调就够了超出这个范围容易出现震荡或者不收敛。这也是为什么很多框架给默认值新手直接照抄不会出大问题但要追求更优效果必须理解这一层。”1.2 优化器的“家族谱系”与选型逻辑业界常用的优化器可以画成几条谱系SGD 系、自适应学习率系Adam 系、以及近年来的 LAMB、LARS 这类大规模分布式优化器。从我的实际体感来看选型逻辑可以总结成一张表场景推荐优化器理由小数据集、CV 分类、需要强泛化SGDmomentum收敛到平坦极小值泛化更好NLP、Transformer、生成式模型AdamW解耦权重衰减适配预训练/微调大规模分布式训练万卡级LAMB / LARS对超大 batch 有专门修正稀疏特征场景、推荐系统Adam / AdaGrad自适应学习率适合高频低频参数混杂强化学习策略梯度Adam 系面对非平稳分布更稳定这里面我想重点展开 AdamW 和 Adam 的区别因为太多人踩过这个坑。原始 Adam 论文里实现 weight decay 的方式是把衰减项直接加在梯度上再用二阶矩归一化。这样做的结果是衰减效果会被“自适应”机制抵消一部分。AdamW 的贡献在于把权重衰减从梯度更新中解耦出来——先计算 Adam 的更新量再单独做一次衰减。这个改动看起来不起眼但在 Transformer 上实测差异非常明显。我的经验是凡是做预训练语言模型的一律用 AdamW用传统 Adam 做 Transformer 微调loss 虽然也能降但最终精度通常差 0.5 到 1 个点。另一个容易被忽略的细节是 batch size 与学习率的关系。像 LAMB 这类专门为大 batch 设计的优化器内部有一个 Trust Ratio 机制——根据参数更新量和参数本身的比例动态调整每个 layer 的学习率。如果你不用 LAMB强行用 AdamW 把 batch size 从 1024 提到 8192学习率就必须线性或开根号缩放否则容易出现“大 batch 训练不收敛或收敛点很差”的问题。1.3 为什么优化器选择会影响模型泛化性泛化性的差异可以从“loss landscape”的角度解释。SGD 加 momentum 的更新轨迹更像一个“精细扫荡”它对梯度的短期波动不敏感因此容易掉进损失曲面中比较平缓的“盆地”这类极小值通常泛化更好。而 Adam 的步长归一化机制容易让参数“穿越”狭窄的陡峭区域最终落脚在更尖锐的极小值上在训练集上表现好测试集上可能打折。我实际测过一个图像分类任务同样的 ResNet50、同样的数据增强、同样的 epochsSGDmomentum0.9, lr0.1, cosine decay最终测试准确率比 Adamlr0.001高出 1.8 个百分点而训练 loss 两者几乎一致。这个数据不是个例在 CIFAR 和 ImageNet 这类任务上SGD 的稳定优势基本是公认的。而到了 Transformer 上情况反过来——AdamW 的无脑鲁棒性更好SGD 需要花很大力气调学习率和 warmup才有机会追平。——— 记住一句话优化器是“目标函数形状的探测器”不同的优化器对同一张 loss landscape 的“感知”方式完全不同。后面要讲的所有调参技巧本质上都是在配合这种感知方式工作。2. 核心参数与隐藏技巧把优化器调到好用的临界点2.1 学习率、weight decay 和 warmup 的联动关系学习率、weight decay、warmup 三者不是独立的旋钮而是一套联动的“无声舞蹈”。很多人单独调一个参数结果效果提升有限就是因为没意识到联动关系。先说学习率。warmup 的本质是让模型在刚开始训练时用较小的学习率先“热身”避免由于初始参数随机导致的巨大梯度把训练炸掉。特别是 Transformer 结构它有 LayerNorm 和残差连接初期梯度的尺度很不稳定没有 warmup 很容易出现 loss 变成 NaN 的情况。一般 warmup 步数设置为总步数的 5% 到 10%超过 20% 会拖慢收敛。再说 weight decay。AdamW 的 weight decay 建议设在 0.01 到 0.1 之间CV 任务常用 5e-4 到 1e-3NLP 微调常用 0.01。如果 weight decay 太小模型容易过拟合太大则模型容量被过度限制loss 降不下去。联动关系体现在如果你把基座学习率调大对应 weight decay 也要适当调大否则正则效果被冲淡。反过来学习率降低后如果 weight decay 不变同等 epoch 下模型容量可能受影响。我用过一个经验法则微调阶段学习率和 weight decay 同步放大或缩小 1.5 倍左右基本能保持平衡。2.2 一阶矩、二阶矩的 epsilon 和衰减率Adam 家族有两个指数移动平均的衰减率betas (0.9, 0.999)。一阶矩对应梯度均值估计0.9 代表它会快速响应最近的梯度变化二阶矩对应梯度平方的均值估计0.999 表示它更“慢热”用来稳定归一化尺度。epsilon 这个参数多数人直接忽略但它其实很值得注意。它在除法分母上加一个极小值防止除以零。默认值通常是 1e-8但在混合精度训练里如果梯度被缩放后再参与更新epsilon1e-8 可能会太小导致更新步长不稳定。我现在的习惯是混合精度训练时把 epsilon 调大到 1e-6 或 1e-7。这能让前期的 loss 曲线更平稳也能减少训练提前发散的概率。衰减率也有值得一提的隐藏场景如果训练数据分布变化剧烈比如强化学习一阶矩衰减率 0.9 可能太慢导致优化器还在用“很久之前”的梯度趋势。此时可以把 betas 调成 (0.8, 0.99)更快遗忘旧梯度。但改动衰减率需要在多个任务上都验证不能只看单条曲线。2.3 混合精度训练中优化器的特殊处理混合精度已经成为训练标配它把前向和反向计算用 FP16参数的更新和解算保持在 FP32。这个过程中最坑的不是算力而是梯度精度。FP16 能表示的数值范围有限小于大约 6e-5 的值会直接变成 0梯度在反向传播经过多层之后很可能出现“下溢出”。应对机制是“损失缩放”loss scaling——训练时把 loss 乘以一个缩放因子通常是 256 或 512梯度也同步放大避免小数值被冲刷为 0。这就出现了一个和优化器直接相关的问题经过 loss scaling 放大后的梯度在传给优化器之前必须除以同样的缩放因子恢复到真实梯度尺度。如果你用的是成熟框架PyTorch AMP、Apex这一步框架帮你做了。但如果你自己手写混合精度逻辑漏掉除法会导致更新量莫名放大几百倍loss 直接飞掉。我踩过另一个隐藏更深的坑优化器参数里保存的“动量”或“二阶矩”是 FP32 状态但在某些加速器实现中model.parameters()里可能同时存在 FP16 的 master weight 和 FP32 的副本。如果你忘了同步这两个副本训练不会报错但模型权重会逐步和优化器状态脱节训练到一半精度突然崩。我的排查手段是每 N 步打印一次参数和优化器状态里的权重数值确认它们保持一致。2.4 梯度裁剪什么时候该用值设多少梯度裁剪不是所有场景都需要但用对了能救你很多次。它的本质是限制一次更新里梯度的最大范数防止梯度爆炸把参数推离合理区域。在语言模型训练里我基本固定用 max_grad_norm1.0 配 AdamW。这个值怎么来的可以根据初始几步的全局梯度范数统计如果多数梯度范数在 0.1 到 0.5 之间max norm 设在 1.0 能提供保护又不至于改变太多更新方向如果任务里长尾数据导致偶发大梯度可以把 max norm 降到 0.5 甚至 0.3但要注意如果设太小会让模型“缩手缩脚”收敛变慢。要注意裁剪的最小单位。PyTorch 的clip_grad_norm_是按所有参数的全局范数进行裁剪而clip_grad_value_是按每个梯度的绝对值裁剪。前者更符合优化器设计的预期因为 Adam 这一类方法关心的是“相对尺度”一致的更新。我的习惯是优先用全局范数裁剪只有在显存受限、只能用 offload 的情况下才考虑值裁剪。3. 实操过程从训练优化到推理优化手把手走一里地3.1 基于 PyTorch 的典型训练优化配置以我最近做的一个中文摘要生成模型为例完整的优化器配置可以这样落地。数据集是 20 万条新闻摘要对模型是 3 亿参数的 Transformer encoder-decoder单卡 A100 显存 80Gbatch size 做不到太大所以用梯度累积模拟 batch size 1024。import torch from torch.optim import AdamW from torch.cuda.amp import GradScaler, autocast model Model(pretrained_path...) optimizer AdamW( model.parameters(), lr1e-4, betas(0.9, 0.999), eps1e-6, weight_decay0.01, ) total_steps int(2e5) # 总步数 warmup_steps int(2e4) # 前10%步数做warmup scaler GradScaler() accumulation_steps 4 # 模拟更大batch size # 每个step内 for i, batch in enumerate(loader): with autocast(): loss model(batch) / accumulation_steps # 损失按累积步数平均 scaler.scale(loss).backward() if (i 1) % accumulation_steps 0: scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) scaler.step(optimizer) scaler.update() optimizer.zero_grad()这里面有几个细节值得拆开讲。第一个是loss model(batch) / accumulation_steps。这一步很多人会做错直接把所有累积 step 的 loss 加起来传回去反向。梯度累加的正确做法是把每一步的 loss 除以累积步数这样累积 N 步之后的梯度总和才等价于一个 N 倍大 batch 的梯度均值。第二个是scaler.unscale_(optimizer)在梯度裁剪之前调用。这个顺序千万不能反先 unscale把 FP16 的缩放梯度恢复为真实尺度再做梯度裁剪最后scaler.step(optimizer)。如果你先裁剪再 unscale裁剪的阈值会被缩放系数干扰导致裁剪过早或失效。第三个是scaler.update()应放在optimizer.zero_grad()之前。严格来说它只影响下一次 loss scale 系数但放在 zero_grad 之前可以避免意外覆盖优化器的状态。3.2 大规模分布式场景下的优化器选择DDP 与 FSDP当模型大到单卡放不下就要考虑分布式训练。最常见的是DistributedDataParallelDDP它本身不改变优化器的更新逻辑只是通过梯度同步让多个 GPU 上的参数保持一致。在这种模式下优化器仍然是“每个 rank 各持有一份”理论上是等价的。但真正的坑出现在模型“大”到连参数都放不进单卡的那种场景此时需要 FSDPFully Sharded Data Parallel。FSDP 会把参数、梯度和优化器状态都分片切到多个 GPU 上。这里我要提醒AdamW 的优化器状态在一片放不下时实际上是从零开始训练的“隐性分片”它对模型收敛没有任何坏处但对显存优化非常关键。如果 FSDP 配 AdamW要注意学习率和 weight decay 的均匀性——理论上 FSDP 分片不影响每个参数的更新逻辑所以在 FSDP 里调参和单卡基本一致。但是如果用了 CPU offload 的优化器状态更新一步的时间可能被拉长数十倍这时候就要重新评估总训练时间成本。还有一个容易忽略的环节梯度同步的时机和梯度裁剪的配合。DDP 默认在反向传播的backward()里进行梯度 all-reduce分布在不同进程上的梯度只有全部就绪后才能被正确读取。如果你自己写梯度裁剪的循环一定要在backward()完成之后、optimizer.step()之前执行不要用子模块分别裁剪否则会破坏 all-reduce 后的全局梯度一致性。3.3 推理阶段的优化量化、剪枝与蒸馏训练做完部署上线时又会遇到新的性能瓶颈。推理优化里我能直接给出落地经验的有三条路量化、剪枝、蒸馏。量化最常见的是 PTQ训练后量化和 QAT量化感知训练。PTQ 的坑在于对 Transformer 模型直接做 8bit 权重量化激活值的动态范围如果没校准好推理精度可能掉 2 到 3 个点。我建议至少用几百条代表性样本做校准校准数据要覆盖真实部署场景的分布。如果 PTQ 掉点严重再上 QAT——QAT 本质上是在训练过程中“模拟量化误差”优化器仍然照常更新只是在计算图里插入了伪量化节点。剪枝分成结构化剪枝直接剪掉整个注意力头或卷积通道和非结构化剪枝把接近零的单个权重置零。从实际部署经验看结构化剪枝对硬件更友好可以真实减少计算量非结构化剪枝在通用硬件上难以提速因为稀疏矩阵计算需要专门引擎支持。剪枝之后通常需要微调此时用较小的学习率如基座学习率的 0.1 倍配合 AdamW能更快恢复精度。蒸馏的方向更省事——直接用大模型的 logits 教小模型。这里的 trick 是蒸馏温度温度太低小模型学不到类别间的分布信息温度太高小模型的 loss 会被过度平滑。一般从 3 开始调教师模型越强温度可以适当提得越高。这里我把三类方法做一个对比方便快速选型优化方法成本精度影响适用场景PTQ INT8低无需训练通常掉 1-3 个点对精度不敏感的上线场景QAT INT8中需要训练掉 0.5 个点左右精度要求高的生产环境结构化剪枝中需要微调视剪枝比例需要真实降低延迟的场景非结构化剪枝低可恢复但难加速极少采用除非有专属硬件知识蒸馏高需要训练小模型大模型精度上限附近需要缩小模型体积的场景3.4 长上下文推理中的缓存优化思路大模型部署中还有一个和标题相关的隐性优化点KV cache。生成式模型在推理时每一步都需要将自己生成的 token 的 KV 值缓存下来保留给后续步骤使用。cache 太大会导致内存膨胀cuda OOM 经常发生在长对话场景。我试过几种可行的策略第一是采用 sliding window attention只保留最近 N 个位置的 KV超出窗口的旧位置被丢弃第二是用 PagedAttention 一类显存管理机制通过非连续显存页分配来规避碎片第三是 context compression把历史多轮的内容提取摘要后替换原始输入。这些优化和“Model-Optimizer”有什么关系因为 cache 策略本质上是在“重新设计模型的内存布局”而优化器的状态管理也遵循同一套思维——都涉及内存复用和计算资源权衡。在很多推理框架中优化 KV cache 以及模型加载时的显存预分配甚至可以反哺训练阶段对显存规划的理解。4. 常见问题与排查技巧实录4.1 训练 loss 下降缓慢或不收敛这个问题的原因可能藏在多个位置但优化器相关的检查顺序很有用先看学习率。如果是 Adam 系从默认 1e-4 往大调一倍或两倍试试如果是 SGD 系试试从 0.1 起步配合 momentum0.9。再看 warmup——如果 warmup 阶段较长且整体步数少模型还没热身完就进入 decay就会觉得“没效果”。再看 weight decay——过大正则让模型容量被压loss 下不去。如果这些都正常建议打印每个模块的参数梯度范数看看是不是某些 layer 的梯度接近 0 导致训练卡住。一个比较隐蔽的情况是有时候梯度范数整体正常但某些层因为初始化过大或者 residual 结构异常梯度在逐层回传时被“稀释”看起来就是 loss 平坦这种时候检查 learning rate 是否对不同层做差异化设置。4.2 训练中期 loss 突然变成 NaN 或发散NaN 先怀疑混合精度。最直接的做法关闭 AMP用 FP32 全精度跑几步对比。如果全精度正常而 AMP 出 NaN说明是 loss scaling 没有处理好或者梯度下溢。此时提升 epsilon 到 1e-6并把 GradScaler 的 growth interval 调大一点给它更多时间判断是否真的稳定。如果全精度也 NaN那就检查数据里是否存在异常值——比如文本里有没有超长 token或者 label 里出现了 NaN。另一个常见原因是学习率过大导致更新跨越到了参数空间的“禁区”。此时先把学习率调低一个数量级再恢复训练如果 loss 能稳定下降再逐步调高。——— 再补充一个很少人注意的点如果你在训练中用了“梯度累积”而累积的步数恰好跨过了no_sync()上下文某些框架里会跳过梯度同步那最后一步的梯度可能只来自部分 micro-batch。我遇到过一次 loss 始终比正常情况高很多的情况排查半天发现是梯度累积步数设置和 DDP 的 no_sync 范围不匹配。这类问题最明显的特征——loss 波动模式出现周期性毛刺。4.3 模型泛化性差训练集效果很好而验证集上不去这个属于典型的优化器与正则化配合问题。先看是不是 weight decay 太小——尤其是 AdamW太小时泛化会肉眼可见地变差。我常用的排查区间是 0.01 到 0.1先设 0.05 跑一组做对照。再看学习率调度。如果最后阶段学习率没有降到一个足够低的值参数在损失曲面的出口附近震荡可能回不到平坦区域。此时可以加一个最后的 cosine decay 或者阶梯式衰减让学习率在最后 10% 步数内降到接近零。从“loss landscape”的角度再补一层SGD 收敛到的平坦极小值往往比 Adam 泛化更好。如果你用 AdamW 做微调发现泛化不佳可以尝试先跑一段 AdamW中后期切到 SGD 低学习率微调。这个方法在目标检测任务上验证过多次能有效提升最终指标。4.4 显存不足让人自闭时的优化器相关走位显存不足的解法里最容易被忽略的是“优化器状态的显存占用”。AdamW 一阶矩和二阶矩分别是一个与模型大小相同的数据结构也就是说同等参数量的模型AdamW 额外占用 2 倍参数空间的显存。如果模型是 10 亿参数FP32 下就是 40 字节每参数参数本身 4 字节乘以 1 2 份优化器状态。解决方案之一是把优化器状态降成 BF16 保存。BF16 的动态范围和 FP32 基本一致只是精度变低对优化器状态的更新影响较小。另一个方案是采用 adafactor 或者 SGD momentum——它们要么隐式估计二阶矩要么不需要保存二阶矩。Adafactor 在训练大模型时常用显存占用比 AdamW 低不少但它的更新行为在某些任务上偏保守。如果连 FP32 的 master weight 都放不下可以考虑用 CPU offload——优化器非常吃显存的那部分放到 CPU 内存里只在需要更新时把参数拷贝到 GPU 算一下。会有额外的通信开销但换显存是划算的。4.5 关于优化器参数初始化的细节曾经有个同事问我优化器初始化之后内部状态是零向量这样不会偏差吗其实 Adam 论文里专门用bias correction处理了这个问题——在最初的几步里一阶矩和二阶矩因为还没有足够多的观测样本所以会偏小这两个修正项会把估计向真实值“拉”回来。PyTorch 默认开 bias correction不需要自己操心但如果手写优化器实现漏掉 bias correction 会导致前几步更新量被异常放大或缩小。还有一个容易被忽略的细节是优化器初始化时传入的参数列表必须与模型参数顺序严格一致。特别是在分布式训练中如果用model.module.parameters()与model.parameters()混用优化器的 param group 可能错位导致某些层根本没被优化或某些层被重复更新。5. 从单卡到生产集群我的优化器调参心得汇总5.1 一个通用的调参启动模板很多人问我看一个陌生模型第一步该怎么做。我的做法是先用一组“稳妥基线”跑通流程然后才动参数。稳妥基线长这样AdamW学习率 1e-4weight decay 0.01warmup 占总数 10%梯度裁剪 max norm 1.0混合精度自适应。对于大多数 CV 和 NLP 任务这组配置不会爆效果也能看。跑通第一版后再根据三条曲线做决策训练 loss 下降速度、验证集指标变化、梯度范数分布。如果训练 loss 下降趋势正常但验证指标先升后降属于过拟合加 weight decay 或用更大的 batch size如果训练 loss 下降明显偏慢就尝试提高学习率或换更大的 batch size 模拟。5.2 不同学习率调度器与优化器的搭配学习率调度器也是优化器选型的重要一环。常用搭配是Transformer AdamW Noam 衰减或者 cosine decayCNN SGD cosine anneal推荐系统 Adam 固定学习率逐步降低。在 AdamW 上我推荐 cosine decay 的变体包括带 warm restarts 的它对语言模型的收敛稳定性和最终精度都比 step decay 好。Noam decay 在大量 NLP 预训练代码里出现它的公式是 d_model 的负 0.5 次方乘上 step 数的负 0.5 次方但同时包含一个 warmup 项来避免初始过大。如果模型参数量小、总步数少Noam 容易让学习率降太快就要降低 decay 系数。5.3 超参搜索中节省成本的经验超参搜索不是“把所有组合跑一遍”而是先固定几个最重要的维度。我认为优先搜索的顺序是基座学习率、weight decay、batch size 与梯度累积步数、warmup 比例。学习率可以先按 3e-5、1e-4、3e-4 三档对比weight decay 按 0.001、0.01、0.1 三档对比。两者组合出 9 组实验通常已经能锁定一个不错的区域。想进一步压缩成本可以用“short-run 验证”——每个实验只跑 20% 的步数对比早期 loss 下降速度和梯度范数的稳定性筛掉明显不可能的组合再对保留下来的组合跑完整实验。这个方法我用了很久很少出现“早期看起来好、后期崩掉”的情况因为优化器问题大多数在早期就会显形。5.4 训练稳定性和可复现性的保底设置最后提两个保底习惯。第一个是在代码里设置所有随机种子Python、NumPy、PyTorch 甚至 CUDA固定优化器初始状态和数据加载顺序第二个是定期保存优化器快照。很多人只存模型权重但如果训练中断后要从 checkpoint 接着原学习率调度和优化器状态继续跑没有 optimizer state dict 就会“脱节”——学习率会重新从初始值开始动量也清零了模型精度大概率出现回退。我现在的做法是每个 checkpoint 里同时保存model.state_dict()、optimizer.state_dict()、scheduler.state_dict()、scaler.state_dict()并且用 epoch 和 global step 编号。恢复训练时按原来的顺序加载就能做到无缝重启。5.5 再分享一个“优化器后处理”的冷门技巧训练完成后可以做个简单的训练后锐化post-training weight averaging——把训练末尾几个 checkpoint 的模型权重直接做平均往往能在验证集上带来小幅稳定提升。这个技巧不改变优化器本身而是利用了优化器“最后阶段在平坦极小值附近晃悠”的特征。权重平均后相当于把最后一段轨迹里的噪声抹掉让参数更接近极小值中心。特别注意做权重平均时要确保 checkpoints 的模型结构完全一致并且是同一训练 run 里的不同阶段跨 run 平均基本就是灾难。我在多个任务上测过这一招平均最后的 3 到 5 个 checkpoint验证集指标通常能提升 0.1 到 0.3 个点而且一般不会变差。速度上几乎不花成本关键是训练时多存几个 checkpoint 而已。这一路从优化器的数学原理到混合精度的细节再到分布式、推理优化和超参排查我踩过的坑远比我写出来的多。但真正让我觉得有价值的是每踩一个坑都会把“为什么”想清楚。Model-Optimizer 表面上是几个类和参数的事本质上却是你对模型训练和推理全流程理解程度的试金石。希望这篇内容能帮你少走几步弯路把省下来的时间花在真正需要创造力的地方。
返回列表