权重共享在模型压缩中的应用:ALBERT的跨层参数共享与通用化分析
2026/7/22 10:19:36 网站建设 项目流程

权重共享在模型压缩中的应用:ALBERT的跨层参数共享与通用化分析

一、参数共享的直觉与ALBERT的激进设计

语言模型的参数规模在过去五年增长了四个数量级(从BERT-base的110M到GPT-4的约1.7T),但其中相当比例的参数是冗余的。一项被广泛引用的分析表明,BERT的12层Transformer中,相邻层之间的注意力权重模式具有高度相似性——第5层的注意力头和第6层的对应头经常学习到相似的语法模式。

ALBERT(A Lite BERT, Lan et al., 2020)基于这一观察提出了一个激进的设计:跨层参数共享。ALBERT的所有Transformer层共享同一组参数——这意味着12层ALBERT-base和24层ALBERT-large的参数量完全相同(因为只有一组参数被复制了12或24次)。与传统BERT相比,ALBERT-base的参数量从110M降至12M(约9倍压缩),同时在下游任务上保持了近90%的性能。

这一设计的核心问题是:如果12层都做相同的事,那为什么需要12层?ALBERT的回答是:虽然参数共享了,但每一层的输入嵌入不同——在第1层处理的表示与第12层处理的表示完全不同。参数共享迫使模型学习"通用的"特征变换,这些变换在不同抽象层次上都有用。

二、参数共享的实现方式与粒度选择

跨层参数共享的实现粒度可以分为三个层次:

全层共享(ALBERT的方案):所有Transformer层的所有参数(自注意力投影矩阵、FFN权重、LayerNorm参数)全部共享。这是最激进的方案,压缩率最高(9×),但性能损失也最大(约3-5%的GLUE基准下降)。

仅注意力共享:共享自注意力的Q/K/V/O投影矩阵,但FFN层保持独立。这基于一个假设:不同层的注意力模式相似(句法级的注意力在学习早期就已形成),但FFN的特征变换是层次化的(从低级特征到高级语义)。

仅FFN共享:与上一种方案对称——让每层有独立的注意力模式,但共享特征变换函数。这种方案的动机是:不同层需要关注不同的token间关系,但将"关注结果"转化为"有用特征"的方式可以是通用的。

""" 三种参数共享粒度的PyTorch实现对比 """ import torch import torch.nn as nn import copy class SharedTransformerLayer(nn.Module): """单层Transformer,可被多次复用以实现跨层参数共享""" def __init__(self, hidden_size: int = 768, num_heads: int = 12, ffn_size: int = 3072): super().__init__() self.attention = nn.MultiheadAttention( hidden_size, num_heads, batch_first=True ) self.ffn = nn.Sequential( nn.Linear(hidden_size, ffn_size), nn.GELU(), nn.Linear(ffn_size, hidden_size), ) self.ln1 = nn.LayerNorm(hidden_size) self.ln2 = nn.LayerNorm(hidden_size) def forward(self, x: torch.Tensor) -> torch.Tensor: # Pre-Norm + 自注意力 + 残差 attn_out, _ = self.attention(self.ln1(x), self.ln1(x), self.ln1(x)) x = x + attn_out # Pre-Norm + FFN + 残差 x = x + self.ffn(self.ln2(x)) return x class ALBERTLike(nn.Module): """类ALBERT模型:支持多种参数共享粒度""" def __init__( self, vocab_size: int = 30000, hidden_size: int = 768, num_layers: int = 12, num_heads: int = 12, ffn_size: int = 3072, sharing_mode: str = "all", # "all" | "attention_only" | "ffn_only" | "none" ): """ Args: sharing_mode: 参数共享模式 - "all": 所有层共享全部参数(ALBERT方案) - "attention_only": 仅共享注意力层,FFN独立 - "ffn_only": 仅共享FFN层,注意力独立 - "none": 每层独立参数(传统BERT) """ super().__init__() self.embedding = nn.Embedding(vocab_size, hidden_size) self.num_layers = num_layers self.sharing_mode = sharing_mode if sharing_mode == "all": # 1组共享参数,复用于所有层 self.shared_layer = SharedTransformerLayer( hidden_size, num_heads, ffn_size ) self.layers = [self.shared_layer] * num_layers elif sharing_mode == "attention_only": # 所有层共享1组注意力参数 self.shared_attention = nn.MultiheadAttention( hidden_size, num_heads, batch_first=True ) self.shared_ln1 = nn.LayerNorm(hidden_size) # 每层独立的FFN self.ffns = nn.ModuleList([ nn.Sequential( nn.Linear(hidden_size, ffn_size), nn.GELU(), nn.Linear(ffn_size, hidden_size), ) for _ in range(num_layers) ]) self.ln2s = nn.ModuleList([ nn.LayerNorm(hidden_size) for _ in range(num_layers) ]) elif sharing_mode == "ffn_only": # 所有层共享1组FFN参数 self.shared_ffn = nn.Sequential( nn.Linear(hidden_size, ffn_size), nn.GELU(), nn.Linear(ffn_size, hidden_size), ) self.shared_ln2 = nn.LayerNorm(hidden_size) # 每层独立的注意力 self.attentions = nn.ModuleList([ nn.MultiheadAttention(hidden_size, num_heads, batch_first=True) for _ in range(num_layers) ]) self.ln1s = nn.ModuleList([ nn.LayerNorm(hidden_size) for _ in range(num_layers) ]) else: # "none" self.layers = nn.ModuleList([ SharedTransformerLayer(hidden_size, num_heads, ffn_size) for _ in range(num_layers) ]) def forward(self, input_ids: torch.Tensor) -> torch.Tensor: x = self.embedding(input_ids) if self.sharing_mode == "all": for _ in range(self.num_layers): x = self.shared_layer(x) elif self.sharing_mode == "attention_only": for i in range(self.num_layers): ln_x = self.shared_ln1(x) attn_out, _ = self.shared_attention(ln_x, ln_x, ln_x) x = x + attn_out x = x + self.ffns[i](self.ln2s[i](x)) elif self.sharing_mode == "ffn_only": for i in range(self.num_layers): ln_x = self.ln1s[i](x) attn_out, _ = self.attentions[i](ln_x, ln_x, ln_x) x = x + attn_out x = x + self.shared_ffn(self.shared_ln2(x)) else: # "none" for layer in self.layers: x = layer(x) return x def count_parameters(self) -> dict: """统计不同共享模式下的参数量""" total = sum(p.numel() for p in self.parameters()) trainable = sum(p.numel() for p in self.parameters() if p.requires_grad) return {"total": total, "trainable": trainable}

三、参数共享的普适性:何时有效、何时失效

ALBERT的成功引出了一个自然的问题:参数共享能否推广到其他模型和任务?实验证据指向一个条件性的结论。

有效场景:在编码器架构的NLP理解任务(GLUE、SQuAD)中,全层参数共享的ALBERT保持了BERT约90%的性能,同时参数减少9倍。这表明:在理解型任务中,深度主要提供"迭代精炼"而非"功能分化"——相同的变换反复应用,每次在上一层的输出上操作,逐步精化表示。

失效场景:在生成任务(如翻译、摘要)中,全层参数共享的性能损失显著更大(约10-15%)。这可能是因为生成任务需要更丰富的层次化特征——浅层处理句法、中层处理语义、深层处理语用——而这些不同层次的特征变换不能由同一组参数有效地完成。

部分共享的折中:将12层划分为3组(浅层组/中层组/深层组),组内共享参数、组间独立。这种"分组共享"策略在参数效率(约3倍压缩)和性能保持(<2%下降)之间取得了更好的平衡,在迁移学习场景中表现尤为稳健。

四、参数共享与知识蒸馏的协同效应

参数共享和知识蒸馏是两种正交的压缩策略——前者压缩模型参数的存储,后者压缩模型的计算图(通过学生-教师框架)。两者的组合可以产生超线性(大于各自独立效果的简单相加)的压缩效果。

TinyBERT的实验表明:使用BERT-base作为教师、ALBERT架构作为学生进行蒸馏,得到的模型在参数量(约5MB)和推理速度上均达到了可用级别,同时保持了BERT-base约95%的GLUE性能。这种协同效应的一个可能解释是:参数共享减少了学生模型的搜索空间(所有层都执行相同的变换),使得蒸馏过程中的优化更容易收敛——教师不需要指导12个不同的层各学习什么,只需要指导一个通用的变换如何在不同深度上发挥作用。

五、总结

ALBERT的跨层参数共享通过将12组独立参数压缩为1组,实现了9倍的参数量缩减。全层共享在理解任务中保持约90%的BERT性能,但在生成任务中性能损失扩大;仅注意力共享或仅FFN共享提供了参数量与性能之间的可调节折中;分组共享(层分组、组内共享)是生产实践中更稳健的选择。参数共享作为一种独立于量化、剪枝、蒸馏的压缩维度,其价值在于与其他压缩方法的正交性——共享+蒸馏的组合可以产生比各自独立使用更大的压缩效果。在部署到资源受限环境(移动端、浏览器WebAssembly)时,参数共享是少数几种不依赖特定硬件加速(如INT8 Tensor Core)即可显著缩减模型体积的策略之一。

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

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

立即咨询