☰
大模型训练省显存8招:从MoE到LoRA,全面降低参数量
2026/10/23 5:29:00 网站建设 项目流程

最近把 DeepSeek 的技术报告翻出来认真啃了一遍,一边看一边回忆起自己以前做模型训练时踩过的坑。大模型训练这个圈子,大家嘴上聊的是效果、刷榜,其实背地里最头疼的永远是两件事:显存不够用,训练太烧钱。这两件事绕来绕去,最后都会回到同一个核心问题——参数量。

老彭这次就顺着 DeepSeek 论文里的技术路线,结合自己实测的经验,把降低大模型训练参数量的方法整理成了 8 招,按“架构级、训练级、工程级”三个梯度归好类。不管你是在做预训练、微调、还是想把已经训好的大模型压缩成小模型,这篇文章里的思路和避坑点都值得参考。

1. 先说清楚:降低参数量到底降的是什么

很多朋友一听“降低参数量”,第一反应是“把模型变小”。这个理解不能说错,但在实际训练里,“参数量”这四个字背后其实藏着好几份不同的账。不先把账算清楚,后面所有优化手段都会用错地方。

1.1 大模型训练里,一份参数有四份账

训练一个模型时,显存里并不是只放着“模型权重”这一份数据。至少要有四份东西同时在吃内存:

  • 模型自身的参数:也就是权重矩阵本身,这是大家最熟悉的一份。
  • 梯度:反向传播时算出来的梯度张量,结构跟参数一一对应。
  • 优化器状态:用 Adam 系优化器的话,每个参数还额外带一份一阶动量(m)和二阶动量(v),这两份的大小跟模型参数一样大。
  • 前向过程中的中间激活值:每条样本过网络时每一层输出的激活值,虽然不算“参数”,但长序列训练下它占的显存往往比参数还大。

我举个具体数字大家就有概念了。假设你训练一个 70 亿参数的模型,用 Adam + 混合精度:

  • 模型参数:FP16 副本约 14GB,FP32 主副本约 28GB,加起来 42GB。
  • 梯度:约 14GB(按 FP16 算)。
  • 优化器状态:一阶动量 + 二阶动量各一份 FP32,28GB × 2 = 56GB。

光这三项就奔着 100GB 去了,还没算激活值。这就是为什么很多人单卡跑 7B 模型,连 OOM 的报错都来不及看。

所以“降低训练参数量”这个目标,实际上可以拆成三种:

  1. 降低模型结构里的总参数量(让模型文件本身变小)。
  2. 降低训练时需要更新的可训练参数量(让优化器状态和梯度变小)。
  3. 降低训练时的显存参数占用(让单卡跑得动更多参数)。

这三种诉求对应的方法完全不同。下面这 8 招,我按这个逻辑分了三个梯队。第 1 到第 3 招是从架构上砍参数,第 4 到第 6 招是从训练策略上省参数,第 7 到第 8 招是从工程上榨显存。

1.2 总参数量 vs 参与计算的激活参数量

还有一对概念必须分清楚:总参数量和激活参数量。

总参数量好理解,就是模型文件里存了多少个权重。但模型前向推理时,并不是每个参数都会参与计算。比如后面要讲的 MoE(混合专家)结构,模型总参数量可能有几百 B,但每个 token 只激活其中一小部分专家。真正参与这次计算的参数,才是决定算力消耗的关键。DeepSeek 论文里反复强调的效率优化,本质上就是在压低“激活参数量”和“训练总开销”的比值,而不是单纯追求总参数量变小。

理解了这两个概念,再看后面的招数,思路会清晰很多。

2. 架构级降参:直接在模型结构上砍

这一梯队的方法最彻底,因为是从模型底层结构上去减少或复用参数。改动一旦定了,后面训练和部署都跟着受益。

2.1 第1招:MoE 稀疏激活,把专家切细

MoE 的核心思想是:不要用一个巨大的稠密网络去处理所有 token,而是准备一堆“专家”子网络,每个 token 只路由给其中排名靠前的几个专家处理。

MoE 不是新东西,但 DeepSeek 论文里有一个细节值得单独拿出来讲:专家粒度。

传统 MoE 的专家通常是一整个大 FFN 模块,每个专家内部的知识高度混杂。你让一个专家既懂代码又懂古诗,它的参数利用率其实很低。DeepSeek 的做法是把专家切得更细,让每个专家专注的“知识面”更窄。同时再加几个“共享专家”,确保每个 token 都能拿到一份通用的基础处理能力。

这样做的好处直接体现在训练开销上:总参数量变多了,但因为每个 token 只激活少量细粒度专家,激活参数量反而比同等容量的稠密模型低很多。训练时的 FLOPs 跟着激活参数量走,于是同样的训练数据量和 batch size,MoE 结构的模型实际计算开销更小。

实操中你要注意 3 个坑:

  • 路由崩溃(routing collapse):训练初期如果负载均衡不做约束,几乎所有 token 都涌向同一个专家,其他专家闲置,模型退化。解决办法是加辅助负载均衡损失,但系数不能太大,太大会把专家的专业性破坏掉。
  • 专家容量(capacity)设置:每个专家能处理的 token 数量有上限,设得太小丢 token,设得太大等于没做稀疏。一般建议从 1.0~1.25 的容量因子起步慢慢调。
  • 细粒度不代表“越细越好”:专家切得太碎,路由选择和参数调度开销会反超收益。我实测下来,专家维度在 64~128 之间是一个比较划算的范围。

MoE 适合的场景很明确:你想拥有超大模型的容量,但训练预算只够中等稠密模型的开销。需要注意的是,MoE 的显存优势在训练阶段,如果你最后要部署推理,总参数量大反而会让加载变慢,需要配合后面讲的量化手段。

2.2 第2招:低秩压缩注意力,把 KV 冗余砍掉

Transformer 里 Attention 模块是参数量和计算量的大头,尤其长序列训练时,KV Cache(缓存每一层的 Key 和 Value 张量)会吃掉海量显存。

标准多头注意力里,每个 head 都要存一份完整的 K 和 V,序列一长,缓存以平方级别增长。DeepSeek 系论文里非常关键的一个设计,就是把 KV 先压缩到低维潜空间,再在这个潜空间里做注意力。这就是通常说的多头潜在注意力(MLA)思路。

这个思路的本质是低秩分解:既然 K、V 矩阵里有大量冗余信息,那就用一个低秩投影把它们压缩成更短的向量。每个 token 只需要缓存这份低维潜向量,而不是完整的一串 K 和 V。序列越长,省得越多。

有朋友问过我:“压缩之后注意力表达会不会变弱?”这个问题要分两面看。低秩压缩确实会丢掉一部分高频细节,所以论文里特意把位置编码的处理单独拆出来,跟低秩压缩解耦,避免位置信息在压缩过程中被抹掉。实际效果是:在支持长上下文的任务上,显存占用大幅下降,模型能力并没有明显退步。

如果你不想从头改注意力结构,也可以把低秩分解当作一个“事后优化”手段:对已经训好的模型做 SVD(奇异值分解),把大的权重矩阵拆成两个小矩阵相乘,然后微调恢复。比如一个 1024×1024 的矩阵,用 128 的秩近似成 1024×128 + 128×1024,参数量直接砍到四分之一。

实操建议:

  • 潜向量维度一般取原始维度的 1/4 到 1/8,低于 1/8 后掉点会比较明显。
  • 低秩压缩后的层建议跟着一起训练,让模型自己学会适应压缩带来的信息瓶颈,不要压缩完直接部署。
  • 长上下文场景收益最大,短文本任务收益有限,别盲目上。

2.3 第3招:跨层参数共享,一套参数跑所有层

Transformer 的每一层都有自己独立的权重,如果让所有层共用同一组权重,模型参数量直接除以层数。这个思路的代表工作是某经典小参数模型:它把主干网络的所有层共享一组参数,最终用很小的参数量达到了当时不错的效果。

跨层共享的好处是立竿见影的:参数量骤降、显存占用骤降、部署文件变小。但它有一个明显的副作用——深层网络本应逐层抽象出不同层次的特征,共享参数后这个能力会被削弱,模型表达能力下降。所以共享参数的模型通常需要训练更久才能收敛到同等水平。

如果全量共享太激进,可以考虑几个变体:

  • 分组共享:比如每 2 层或每 3 层共享一组参数,在压缩和表达能力之间折中。
  • 只共享部分子层:比如共享 Attention 权重但保持 FFN 独立,或者反过来。
  • 周期性共享:每隔几层用一个循环结构,类似 RNN 的思路。

实操中我比较推荐从分组共享入手,而不是一上来就全共享。全共享模型的收敛速度会慢很多,训练总步数可能需要翻倍,最后省下的训练成本可能又被额外的训练步数吃回去,得不偿失。

另外注意:跨层共享对训练稳定性有影响,学习率建议比普通 Transformer 调低 20%~30%,否则深层梯度容易异常波动。

3. 训练级降参:让模型少学一点多余的东西

架构级方法适合从零设计模型。但更多时候,你手里已经有一个现成的大模型底座,需要的只是“用更少的可训练参数达到接近的效果”,或者“把大模型浓缩成一个小模型”。这时候就要用训练策略层面的手段。

3.1 第4招:知识蒸馏,让大模型带出小模型

这是最直观的一招:不去从头训一个小模型,而是让一个大模型当“老师”,小模型当“学生”,学生模仿老师的输出。

蒸馏的关键在于“软标签”。老师模型给出的不是硬性的 A/B/C 答案,而是一组概率分布。比如“这张图是猫”的概率 0.7、是狗的概率 0.2、是狐狸的概率 0.1,这组分布里包含了老师对样本的理解——狗长得像猫是因为都有耳朵,狐狸比狗更像猫是因为体型相近。学生模型学着拟合这个分布,能接触到比硬标签丰富得多的监督信息。

实际做蒸馏时,有几个超参需要特别小心:

  • 温度 T:T 越大,概率分布越平滑,学生能学到更多细粒度关系;T 太小就退化成硬标签。一般 2~8 之间比较常用。
  • KL 损失权重:软标签损失和真实标签损失的配比常用 0.5 起步,逐步调。
  • 数据分布:不要只拿老师训过的数据蒸馏,要混一些新鲜的真实数据,否则学生只会复制老师的偏见。

DeepSeek 系列论文里有一个特别值得参考的做法:不只用老师的最终输出做蒸馏,而是把老师生成的完整推理链(一步步的中间思考过程)拿来当训练样本。学生学习的不只是答案,而是“怎么想到答案”的路径。这个思路对推理类任务的效果提升非常明显。我当时看完就照着这个思路做了一个小规模实验,同样参数量的小模型,加了推理链蒸馏的版本在逻辑推理任务上的效果明显高出一截。

3.2 第5招:结构化剪枝加继续训练,瘦身之后要复健

剪枝的思路是“先训大,再变小”:在大模型训练完成后,把贡献不大的参数删掉,得到一个参数量小得多的模型,然后再做短时间的继续训练恢复性能。

剪枝分非结构化剪枝和结构化剪枝。非结构化剪枝是把权重矩阵里接近 0 的元素置零,矩阵形状不变,省的是计算稀疏度,但显存和推理加速有限。结构化剪枝则是直接删掉一整行、一整列、整个 head 或整层网络,参数文件才会真正变小。

怎么判断哪些部分可以剪?三种主流依据:

  • 权重幅值:绝对值小的权重对输出影响小,优先剪。最简单,但不算准。
  • 梯度敏感度:计算每个参数对 loss 的敏感程度,敏感度低的优先剪。比幅值更可靠,但计算开销大。
  • 激活统计:看每个 head / 通道在真实数据上的激活频率,长期处于“低激活”状态的组件优先剪。

我的个人经验是:光看幅值剪,效果偏差最大(因为有些幅值小的参数在反向传播中作用很大);用梯度信息辅助判断最靠谱,但要注意梯度噪声,最好在多个 batch 上求平均。

剪枝之后一定要继续训练,这个环节我习惯叫“复健”。复健时要把握三个关键点:

  • 学习率策略:剪完一个阶段后,把学习率回退到初始阶段的 1/10 左右,再按余弦退火走完。
  • 分阶段剪:不要一次性剪掉 50% 参数,而是每轮剪 10%~20%,复健几天再剪下一轮,这样每个阶段模型都能缓慢适应。
  • 配合蒸馏:如果手里有剪枝前的大模型,复健时让它当老师,效果比单纯用真实标签训练稳定得多。

需要提醒的是,剪枝本身并不能降低训练阶段的成本——你是在大模型训完或者训练中途才动手的。它的真正价值在于:你只需要训一次大模型,就能得到好几个不同规格的小模型,省下的是一次次从零训练小模型的钱。

3.3 第6招:冻结主干,只训练关键参数

如果你只是想在一个已经训好的大模型上做下游任务微调,全量更新所有参数其实是件很划不来的事。几十 B、上百 B 的参数,光是把优化器状态存下来,显存就爆了。参数高效微调(PEFT)的思路就是:冻结绝大部分主干参数,只训练一小撮精心挑选的参数。

这套思路里最有代表性的就是低秩适配(LoRA)了。LoRA 的基本做法是:原始权重矩阵保持不变,在旁边插入两个小矩阵 A 和 B,用它们的乘积去模拟权重的变化量。训练时只更新 A 和 B,原始权重完全冻结。A 和 B 的维度从一个很小的秩开始,比如 8、16、32。假设原始矩阵是 4096×4096,LoRA 用秩 16 的话,新增参数量只有 4096×16 + 16×4096 ≈ 13 万,相比原来的 1600 多万,几乎可以忽略不计。

这套方法的效果是有理论底子的:预训练大模型本身已经学到了非常强的通用特征,下游任务通常只需要在低维子空间里做少量调整。你不需要重新教会它语言,只需要让它适配一个特定任务的输出风格。

实操中几个容易吃亏的点:

  • 初始化:矩阵 A 用随机高斯初始化,矩阵 B 初始化为全零,这样才能保证训练刚开始时 LoRA 分支不影响原模型输出。
  • 学习率:冻结主干模型后,可训练参数的量级变小了,学习率通常可以比全量微调大一个数量级,但要从 1e-4 左右起步去试。
  • 秩的选择:不是越大越好,我自己试下来,很多任务秩 16~32 就够用,往上加到 128 收益有限,反而增加过拟合风险。
  • 混合策略:如果数据分布跟预训练差异很大,单靠冻结主干可能不够。这时候可以只冻结前几层,放开最后几层全量微调,或者把 LoRA 和部分层更新混着用。

这一招的本质是“降低可训练参数量”,优化器状态和梯度的显存开销会大幅下降。比如全量微调一个 7B 模型可能需要 60GB 以上的显存,用 LoRA 之后十几 GB 就能跑起来,还是非常有吸引力的。

4. 工程级降参:把显存里的参数副本榨干

第三梯队的方法不改变模型结构,也不改变训练逻辑,而是从工程实现层面减少训练时驻留在显存里的参数副本。适合那些模型结构已经定了、但又想硬塞进有限显存里的场景。

4.1 第7招:低精度表示,用 FP8/BF16 给参数减负

这是最直接的“省显存”手段:用更少比特去表示同一个参数。

全精度训练早期普遍用 FP32,一个参数占 4 字节,7B 模型光参数就 28GB,单卡完全没法玩。混合精度训练把模型副本改成 FP16(2 字节)或 BF16(2 字节),同时保留一份 FP32 的“主副本”用于参数更新,内存占用立刻减半。

关键问题是:FP16 和 BF16 怎么选?FP16 的精度更高,但它的数值范围有限,训练大模型时激活值和梯度的动态范围很容易超出边界,导致溢出(loss 突然变 NaN)。BF16 的尾数精度低,但动态范围跟 FP32 几乎一样宽,不会溢出——大模型训练里“不溢出”往往比“更精确”重要得多。所以我个人在大模型训练里优先选 BF16 而不是 FP16。

再进一步就是 FP8 了。把前向传播里的激活和权重压到 FP8,反向传播里的权重和梯度也压到 FP8,只在优化器更新时保持高精度。这个方案能省一半显存,但实现细节极其敏感。

我的建议是:FP8 训练不要盲冲。如果你没有专门的底层优化经验,先跑通 BF16 双副本训练,再考虑 FP8;而且初期最好只在某些敏感度低的层启用 FP8,让模型稳定后再逐步推广。

还有一个工程技巧常常被忽略:优化器里的 Adam 状态也可以做低精度。普通 Adam 的 m 和 v 都是 FP32,一进一出就是 8 字节/参数。用带有误差补偿的 8-bit 优化器,可以把优化器状态降到 1~2 字节/参数,代价是偶尔出现精度损失,但配合误差反馈机制效果基本等同 FP32。

不同精度方案的内存占用对比:

方案模型权重梯度Adam优化器状态每参数总占用
FP32全精度4B4B8B16B
FP16/BF16混合精度2B+4B主副本2B8B约13B(去掉主副本复用后实测更少)
BF16+8-bit Adam2B+4B主副本2B2B约7B
FP8+8-bit Adam1B+4B主副本1B2B约6B

表格里的“每参数总占用”是粗算,实际还要算上激活值的临时显存,但优化方向已经很明确了。

4.2 第8招:优化器状态瘦身与分片,让单卡参数变小

如果说低精度是把“每个参数吃多少字节”压下来,分片技术解决的是另一个维度:单卡上到底要放“多少个参数”。

还是回到前面那个 7B 模型,用 Adam + BF16 混合精度训练,光参数和优化器状态的显存需求就已经接近 70GB。单卡放不下怎么办?最朴素的想法是拆开多张卡,每张卡只放一部分参数、梯度和优化器状态。这就是 ZeRO 系列分片技术在做的事。

ZeRO 按阶段划分:

  • ZeRO-1:把优化器状态切分到各卡,每卡只维护自己负责的那部分参数的 m 和 v。
  • ZeRO-2:优化器状态 + 梯度都切分。
  • ZeRO-3:优化器状态、梯度、模型参数全部切分,前向传播需要用到哪层参数,哪层就临时从别的卡拉过来。

配合 Offload 机制,还能把暂时用不到的参数状态挪到 CPU 内存或 NVMe 硬盘里。这样单卡显存占用会被压得特别低,代价是卡间通信变多、训练速度下降。

很多朋友对“分片”有个误解,觉得“参数变小了,训练变便宜了”。分片并没有减少总参数量,也没有减少总计算量,它只是把显存压力从“单卡扛”变成了“多卡分摊”,该烧的卡还是得烧,只是从“一张大卡”换成了“多张小卡”。这一点在选方案之前必须想清楚。

配套的还有两个常用手段:

  • 梯度累积:小 batch 攒够多次再统一更新参数,能模拟大 batch 的稳定性,顺便减少前向/反向的显存峰值。
  • 激活重算(Activation Checkpointing):不缓存所有层的中间激活,反向传播时临时重算,用算力换显存。长序列训练里这个技巧往往能省下比参数本身更大的显存。

工程级的招数组合起来,最极限的情况是:模型参数用 BF16、优化器用 8-bit 低精度、ZeRO-3 全分片 + CPU Offload、激活全重算,最终把单卡显存占用压到只剩一个很小的“驻留参数量”。代价是训练时间明显拉长,属于典型的“用时间换资源”。

5. 这8招怎么组合使用:实操顺序与避坑速查

前面 8 招单独看都成立,但真正到项目里,很少人会只用一招。不同训练阶段适合的手段完全不同,组合顺序也很有讲究。

5.1 不同阶段的组合建议

我做项目时一般这样分:

  • 从零开始预训练一个大模型:以架构级为主、工程级兜底。先在结构上用 MoE 或低秩压缩把“激活参数量”降下来,再用 BF16/FP8 + ZeRO 分片把显存塞进现有卡群。路线是:架构定容量,精度和分片定能不能跑。
  • 在已有大模型上做下游微调:只需要第 4、6 招。LoRA 冻结主干解决显存问题,如果微调数据质量高,再叠一层蒸馏,用小模型把老师的能力沉淀下来。
  • 已经训好一个超大模型,想部署成小规格:走第 4、5 招。先蒸馏出一个中等规格模型,再结构化剪枝逐步瘦身,剪几轮复健几轮,最后配合量化部署。

组合推荐矩阵:

场景首选招数辅助招数不推荐
从零预训练大模型第1招MoE、第2招低秩压缩第7招低精度、第8招分片第4招蒸馏(没有老师)
微调已有底座第6招冻结主干第3招跨层共享(若可改结构)第5招剪枝(破坏预训练特征)
大模型变小模型第4招蒸馏、第5招剪枝第7招量化第1招MoE(部署加载仍是大体量)
显存极度紧张第8招分片+Offload第7招低精度无,全都要上

5.2 我踩过的坑和排查思路

第 1 招 MoE 的路由崩溃:症状是训练若干 step 后,某个专家被激活的频率变成 0。排查时先看辅助负载均衡损失是否正常下降,再看 TopK 路由的 logits 是不是出现极大值导致 softmax 饱和。我一般先降低专家容量因子、加大负载均衡损失的权重,还是不行就检查学习率,MoE 的训练通常需要比稠密模型更低的学习率。

第 4 招蒸馏时温度过高:T 太大,软标签的分布会过于平滑,学生模型学到的是“什么都像一点点”的模糊知识,下游任务指标不升反降。排查时首看 KL 损失曲线,如果 KL loss 一直压不下去,就把 T 往小调。经验值是先从 4 开始,看指标变化再上下浮动。

第 5 招剪枝后 loss 爆炸:最常见的原因是一次剪太多。剪枝后模型输出分布剧烈偏移,紧接着用大学习率复健,loss 不炸才怪。正确做法是剪完一个阶段后先把学习率降到原 1/10,跑几百个 step 观察 loss 稳定性,稳定后再逐步恢复。

第 7 招 FP8 训练 loss spike:FP8 的损失缩放(loss scaling)策略必须比 FP16 更保守。我习惯把初始 scale 设到 FP16 的数倍,然后每检测到一次溢出就把 scale 减半,连续若干步不溢出再加回,这个循环跑稳之后基本不会再 timestep 式爆 loss。

第 6 招冻结主干微调时验证集过拟合:这个坑很多人中招。LoRA 可训练参数少,模型容量有限,训练集上 loss 降到很低、验证集指标却不动。这时不一定要加参数,可以先降学习率、加早停、把训练轮数压到原来的 60% 试试;实在不行再考虑把秩往上提一点。

5.3 一张速查表直接抄

招数核心思路省的是什么主要代价/风险推荐场景
1. MoE稀疏激活细粒度专家+稀疏路由激活参数量、训练算力总参数大、路由易崩溃从零预训练超大容量模型
2. 低秩压缩注意力KV压进低维潜空间KV缓存、注意力参数压缩过度会损失表达长上下文的预训练/微调
3. 跨层参数共享多层共用一套权重模型总参数量表达能力下降、收敛变慢参数极度受限的部署
4. 知识蒸馏大模型带小模型小模型的训练/推理成本依赖老师的质量已有大模型,需要小模型
5. 结构化剪枝+复健删除冗余结构再训练模型实际参数量训练成本不降、恢复训练需要时间大模型后处理/多规格出口
6. 冻结主干+LoRA只训低秩增量可训练参数量、优化器状态容量有限,难学全新能力底座微调、下游任务适配
7. 低精度表示FP8/BF16+8-bit优化器参数内存占用loss溢出、精度损失一切显存紧张的训练
8. 优化器分片/Offload参数状态多卡分摊单卡显存占用通信开销大、速度变慢单卡放不下模型时

这 8 招我每招都单独测试过,也在不同项目里交叉组合过。我的体感是:大模型训练里的“省参数”更像是一种权衡艺术,你永远不可能一边把所有成本维度全部压到最低,一边还指望效果一分不掉。真正靠谱的做法是设定一个明确目标——到底是要省显存、省训练时长、还是省最终部署体积——然后围绕这个目标选两到三个招数组合,而不是一次把 8 招全部压上去。每新增一个优化手段,就多一组需要维护的超参数和潜在风险,项目越到后期,这种复杂度往往会比显存问题更让人头疼。

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

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

立即咨询