在本博客中,我们将正式开始xmixers的旅程,我们将讨论一个问题,初始化对于模型的收敛性的影响。

模型架构

这里我会选择四个模型架构进行研究(这里的LLaMA使用了LayerNorm,事实上,我发现LayerNorm和RMSNorm效果基本一致):

  • LLaMA
    • 事实上,这里和标准的Llama有一些不同,我会在最后讨论该问题;
  • GPT
    • Transformer with learnable pe,和nanoGPT中相同;
  • GPT-sincos;
    • Transformer with sincos pe;

另一方面,为了验证我的实现是否正确,我还将nanoGPT中的GPT模型作为我的baseline。

我们会测试三个尺寸的模型,分别是:

  • small: 124M
  • medium: 350M
  • large: 774M

具体参数请参考nanoGPT.

实验

Method1

Method1来自于huggingface的初始化策略:

  • 使用$\mathcal N(0, 0.02^2)$初始化所有层;

下面是学习曲线的对比:

从上图中我们可以看到几个现象:

  1. GPT和GPT-baseline的loss相差非常多,但是这两者应该是一样的模型,是什么造成了这个不同?
  2. LLaMA相比于GPT-baseline更好;
  3. GPT-sincos的loss不太稳定,并且效果远逊于GPT-baseline,这体现了Learnable PE的优势;

因为发现了现象1,我不得不仔细对比nanoGPT和xmixers关于GPT的实现不同,经过仔细对比,我发现了只有初始化部分略有不同,nanoGPT添加了如下的初始化

# apply special scaled init to the residual projections, per GPT-2 paper
for pn, p in self.named_parameters():
	if pn.endswith('c_proj.weight'):
		torch.nn.init.normal_(p, mean=0.0, std=0.02/math.sqrt(2 * config.n_layer))

即将Attention, FFN层的最后一个Linear层用$\mathcal N(0, (0.02/\sqrt{2L})^2)$初始化,其中$L$是Transformer Block的数量。

Method2

结合之前的分析,我们得到了Method2:

  • $\mathrm{std} = 0.02$;
  • 使用$\mathcal N(0, {\mathrm{std}}^2)$初始化Attention, FFN层的最后一个Linear层以外的层以及Embedding层;
  • 使用$\mathcal N(0, ({\mathrm{std}}/\sqrt{2L})^2)$初始化Attention, FFN层的最后一个Linear层;

我们重新进行实验,下面是学习曲线的对比:

可以观察到如下现象:

  • GPT和GPT-baseline的loss非常接近,说明关于层的scale起了作用;
  • LLaMA的loss变化不大,说明关于层的scale对于LLaMA影响不大;
  • GPT-sincos直接spike了,看起来关于层的scale对于GPT-sincos起了反作用;

因为我希望对齐GPT和GPT-basline,所以在后续的实验中,我将默认添加关于层的scale,然后通过别的办法解决GPT-sincos的spike的问题。

Method3

最近一篇论文指出,使用truncated normal来初始化weight会让训练效果更好,并且减少spike,代码为:

elif (
    self.config.init_type == 1
):  # credit to https://arxiv.org/pdf/2409.02060#page=14.84
    std = self.config.init_std
    trunc_std = 3 * std
    if isinstance(module, nn.Linear):
        nn.init.trunc_normal_(
            module.weight, mean=0.0, std=std, a=-trunc_std, b=trunc_std
        )
        if module.bias is not None:
            nn.init.zeros_(module.bias)
    elif isinstance(module, nn.Embedding):
        nn.init.trunc_normal_(
            module.weight, mean=0.0, std=std, a=-trunc_std, b=trunc_std
        )
        if module.padding_idx is not None:
            nn.init.zeros_(module.weight[module.padding_idx])

由此我们得到了Method3:

  • $\mathrm{std} = 0.02$;
  • 使用$\mathrm{clip}(\mathcal N(0, {\mathrm{std}}^2),-3\mathrm{std}, 3\mathrm{std})$初始化Attention, FFN层的最后一个Linear层以外的层以及Embedding层;
  • 使用$\mathrm{clip}(\mathcal N(0, ({\mathrm{std}}/\sqrt{2L})^2),-3{\mathrm{std}}/\sqrt{2L}, 3{\mathrm{std}}/\sqrt{2L})$初始化Attention, FFN层的最后一个Linear层;

我们进行实验,下面是学习曲线的对比:

很不幸的是,GPT-sincos的loss依然spike了。然后我们进一步对比一下method2和method3的效果:

从上图中我们可以看出,method3效果相比于method2更好。

Method4

我们先将注意力转移一下,研究Attention部分的初始化,这里我测试了flafairseq关于Attention的初始化Method,具体分别为:

def _initialize_weights(self, module):
    if getattr(module, "_is_hf_initialized", False):
        return

    if self.token_mixer_init_type == 0:
        pass
    elif self.token_mixer_init_type == 1:  # fla init
        if isinstance(module, nn.Linear):
            nn.init.xavier_uniform_(module.weight, gain=2**-2.5)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
    elif self.token_mixer_init_type == 2:  # fairseq init
        if isinstance(module, nn.Linear):
            nn.init.xavier_uniform_(module.weight, gain=2**-0.5)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
    module._is_hf_initialized = True

method 4和method2结合,由此我们得到了Method4.1:

  • $\mathrm{std} = 0.02$;
  • 使用$\mathcal U(-a, a)$初始化$W_q,W_k, W_v$;
    • $a$根据方法的不同进行选择;
  • 使用$\mathcal N(0, {\mathrm{std}}^2)$初始化FFN层的最后一个Linear层以外的层以及Embedding层;
  • 使用$\mathcal N(0, ({\mathrm{std}}/\sqrt{2L})^2)$初始化Attention, FFN层的最后一个Linear层;

这里我们只测试了LLaMA模型,下面是学习曲线的对比:

可以看到,效果上:fairseq > 无特殊初始化 > fla。

作为了一个补充实验,我们将fairseq attention init和method 3进行结合,得到Method4.2:

  • $\mathrm{std} = 0.02$;
  • 使用$\mathcal U(-a, a)$初始化$W_q,W_k, W_v$;
  • 使用$\mathrm{clip}(\mathcal N(0, {\mathrm{std}}^2),-3\mathrm{std}, 3\mathrm{std})$初始化Attention, FFN层的最后一个Linear层以外的层以及Embedding层;
  • 使用$\mathrm{clip}(\mathcal N(0, ({\mathrm{std}}/\sqrt{2L})^2),-3{\mathrm{std}}/\sqrt{2L}, 3{\mathrm{std}}/\sqrt{2L})$初始化Attention, FFN层的最后一个Linear层;

学习曲线的对比如下:

这次GPT-sincos没有spike,GPT反而spike了,LLaMA一如既往的稳定。

最后,我们将method 4.2和method 3以及method 4.1的LLaMA进行对比(剔除了spike的部分):

从上图中可以看出(注意这里LLaMA method 4.1实际上和method 4.2几乎重合了):

  • 对于LLaMA来说,初始化方案都差不多;
  • GPT依然弱于LLaMA;
    • 注意method 3是效果最好的GPT版本,其他版本要么spike,要么弱于method 3;
  • GPT-sincos在method 4.2情况下可以收敛;

现在我们关于GPT, GPT-sincos以及LLaMA都有一个可以收敛的版本,我们分别列出:

  • GPT:method 2,method 3;
  • GPT-sincos:method 4.2;
  • LLaMA:所有版本都可以收敛;

我们现在将模型scale,学习曲线如下:

Medium:

Large: add llama 4.2 test

可以看到,只有LLaMA顺利训练到774M的模型,所以我们还需要探索新的初始化方案。我们对现在可以收敛的版本进行一个总结:

Model/Size Small Medium Large
GPT method 2 = method 3 method 2 NA
GPT-sincos method 4.2 NA NA
LLaMA All All All

Method5

论文提供了另一种初始化方案,将其和method 4结合得到method 5:

  • $\mathrm{std} = (2/5/d)^{1/2}$;
  • 使用$\mathcal U(-a, a)$初始化$W_q,W_k, W_v$;
    • $a$使用fairseq的版本;
  • 使用$\mathcal N(0, {\mathrm{std}}^2)$初始化FFN层的最后一个Linear层以外的层以及Embedding层;
  • 使用$\mathcal N(0, ({\mathrm{std}}/\sqrt{2L})^2)$初始化Attention, FFN层的最后一个Linear层;

实验结果如下:

可以看到GPT和LLaMA的区别都很小,而GPT-sincos spike了,所以目前可以收敛的版本为:

现在我们关于GPT, GPT-sincos以及LLaMA都有一个可以收敛的版本,我们分别列出:

  • GPT:method 2,method 3,method 5;
  • GPT-sincos:method 4.2;
  • LLaMA:所有版本都可以收敛;

Method6

Metaseq和论文指出,对于embedding层之后的项乘以$\sqrt{d}$的常数(embedding scale)可以增加训练稳定性,我们继续进行实验:

从图中可以看出,除了GPT-sincos场景,embedding scale都带来了副作用(GPT带scale的两组曲线几乎重合)。

现在切换到Medium版本:

Large版本 (doing):

我们对收敛性再次进行总结:

Model/Size Small Medium Large
GPT method 2, 3, 5 > method 3, 5 scale method 2 > method 5 scale method 5 scale
GPT-sincos method 4.2 scale > method 4.2 NA method 4.2 scale
LLaMA All All All