Pytorch Parameters 结构与 Muon 的调用
近期想测试 Muon 优化器的效果,看了下 Muon 官方 Github 的实现,发现无法像 Adam 或者 AdamW 那样直接简单地调用。还是要学习一下 pytorch 中 model.parameters() 的结构,最终搞明白如何使用 Muon 优化器。
背景
众所周知,我们一般创建 Adam 等优化器时只需要把模型内部的参数 model.parameters() 传给优化器就可以很简单地创建一个优化器,例如以下代码:
=
虽然大多数优化器都能以上面这样简单的方式进行创建,Muon 却不太一样。Muon 是针对二维以及更高维张量的优化器,而剩余的一维张量(例如偏置、Embedding层、输出层)则交给 Adam。按照其官方仓库的说明,要把 AdamW 替换为 Muon 得进行一定程度的修改:
# optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, betas=(0.90, 0.95), weight_decay=0.01)
# 要把 AdamW 替换为 Muon,则需要使用以下代码:
=
=
=
=
=
可以看出上面的代码涉及到了一些 Pytorch 中 Parameters 的结构,对于没咋写过优化器的炼金师(我)来说还是有点儿不熟悉的,而且可能要根据项目需求进行进一步修改。所以要正确使用 Muon 优化器,还是得充分理解 Pytorch 中 Parameters 的结构。
Model.Parameters() 的本质
Parameter 对象:在 Pytorch 中,一个神经网络模型(对应 torch.nn.Module 类)由多个层组成,每个层又包含一些可以学习的参数(主要是权重 weights 和偏置 bias),这些参数被封装为 torch.nn.Parameter 对象。torch.nn.Parameter 对象本质上是特殊的 Tensor,其与普通 Tensor 的区别在于其被注册为模型的参数,在调用 loss.backward() 时 Pytorch 会自动计算其对应的梯度。
model.parameters() :model.parameters() 返回一个迭代器(Iterator),我们可以用它遍历模型中注册的 torch.nn.Parameter 对象。大多数情况下,我们只需要使用这个迭代器就可以创建一个优化器,例如:
= # 包含 weight 和 bias
= # 一个独立的参数
=
# 输出:
# 参数形状: torch.Size([5, 10]), 需要梯度: True <-- self.linear_layer.weight
# 参数形状: torch.Size([5]), 需要梯度: True <-- self.linear_layer.bias
# 参数形状: torch.Size([3]), 需要梯度: True <-- self.lone_parameter
Parameter Groups 参数组:除了使用上述的 parameter iterator 创建优化器外,Pytorch 还支持传入一组不同配置的参数(一个字典组成的列表),列表中每个字典定义了一个参数和对应的超参,包含 params (torch.nn.Parameter 对象的列表,而非迭代器)以及训练这些模型参数的超参(例如 lr、weight_decay 等)。例如外面在前面例子的基础上希望对每层参数使用不同的学习率,则可以按下面的方式定义优化器
=
=
=
# 注意 params 键对应的值必须为列表
=
= # 使用SGD举例
注意:model.parameters() 返回的只能返回 iterator。parameter groups 必须要在外部定义。
model.named_parameters() :为了更方便地筛选和命名参数, Pytorch 还支持 named_parameters() 方法,类似于 parameters() 方法,其返回了一个迭代器,但是每步迭代得到的的元素是 (name, parameters) 的元组。那 name 是哪里来的呢?Pytorch 有一套自己的命名方式:
- 直接赋值:例如
self.output_layer = nn.Linear(10,2),则对应的两个参数的名称为output_layer.weight 和output_layer.bias。再比如说self.my_param = nn.Parameter(torch.rand(5)),则对应的名称为my_param -
nn.Sequential 结构:例如self.features = nn.Sequential(xxx, xxx),则获得的参数的名称为features.0.weight、features.0.bias、features.1.weight … 其中中间的数字表示第n 层。 -
nn.ModuleList 或者 Python 列表/字典:类似于nn.Sequential。
我们可以使用 'pattern' in name 来确定参数的 name 中是否包含某个 pattern。例如我们也可以用 name 的方式来构建 param groups:
=
=
=
=
=
=
Muon 优化器实战
现在我们拥有了所有必要的知识,可以来解读和使用 MuonWithAuxAdam 了(见 Muon/muon.py at master · KellerJordan/Muon),从代码和文档我们可以看出:
-
Muon 是一个混合优化器:其内部同时实现了Muon 和Adam 的更新逻辑; -
Muon 必须传入参数组(不能传入model.parameters() 或者model.named_parameters()),参数组中使用键'use_muon': True/False 来确定是否使用 Muon 算法。
我们再次来理解 Muon 官方仓库给出的范例
=
=
=
=
=
-
model.body、model.head、model.embed 是模型中定义的属性,但这并非是所有模型都有这些属性,因此要根据自己的模型去修改。 - 这里把
2 维及以上的参数设置为hidden_weights 使用Muon 进行优化,1 维偏置以及head、embed 的参数使用Adam 优化。 - 具体怎么控制是否使用
muon:创建一个参数组,并使用use_muon 键控制是否使用muon 进行优化。
例如比较常用的,筛选掉维度 >=2 以及 embedding 层的用法可以是:
# 筛选出所有维度 >= 2 的参数,通常是权重 (weights)
=
# 剩余的所有参数都交给 AdamW
# 包括:所有维度 < 2 的参数 (biases, layernorm gains) 以及 Embedding 层的参数
=
# 检查是否所有参数都被分配了
assert == +
=
=