大模型-GLM-130B

最近利用空闲时间学习了一下大模型,下面就GLM-130B相关文章进行分享,一起学习进步。

论文信息

1 GLM

论文题目:

GLM: General Language Model Pretraining with Autoregressive Blank Infilling

论文地址:

https://aclanthology.org/2022.acl-long.26

代码地址:
https://github.com/THUDM/GLM

2 ChatGLM


论文题目:

GLM-130B: AN OPEN BILINGUAL PRE-TRAINED MODEL

论文地址:

https://arxiv.org/abs/2210.02414

代码地址:

https://github.com/THUDM/GLM-130B/tree/main

GLM

1 介绍

GLM(General Language Model)对已有的预训练范式进行改进,提出了一种基于自回归的,并引入了填空机制的范式,该范式相比于已有的自编码模型(BERT等)、自回归模型(GPT)和编码器-解码器模型(T5等)在NLU、有条件NLG和无条件NLG三类任务上有着更优的效果。

上图就是对GLM任务的描述,在数据处理时,遵循自编码的思想,随机将绿色部分遮盖,遵循自回归的思想,生成对应的字。而且从图中的注意力连接线可以看到,在生成部分保持着单向注意力,在蓝色部分则是双向注意力。GLM将NLU任务制定为包含任务描述的完形填空问题,然后通过自回归来生成回答。作者考虑到”blanking filling“已经在T5中使用过,所以基于此提出了两点改进,分别是”span shuffing“和2D位置编码。

2 实现
2.1 数据处理

GLM在处理数据时会随机采样部分spans,假设 x = [ x 1 , . . . , x n ] x=[x_1,...,x_n] x=[x1,...,xn]作为模型的输入,采样到的spans为 { s 1 , . . . , s m } \{s_1,...,s_m\} {s1,...,sm},每个span对应x中的一个连续片段,并且使用[MASK]这个token来代替每个span。

上图描述了GLM预训练的实现方法,图(b)表示数据的处理过程,从数据数据 x x x中随机选择了两个span(作者从 λ = 3 \lambda=3 λ=3的泊松分布中采样,并且持续采样直到句子中至少15%的token被遮掩), [ x 3 ] [x_3] [x3] [ x 5 , x 6 ] [x_5,x_6] [x5,x6],并将被遮盖的句子与选择的span分为partA和partB,在模型训练过程中,GLM需要通过自回归的方式生成partB,对于B中的每个span以[S]作为开头,并附加[E]作为输出,以区分结尾,同时需要注意的是,对于partB,并不是保持原有的语序顺序,如图中是以 [ x 5 , x 6 , x 3 ] [x_5,x_6,x_3] [x5,x6,x3]作为顺序的,也就是说在拼接partB时打乱了序列的顺序。

同时,根据图(c)可以发现,GLM在使用注意力时,partA部分使用的是双向,pabrtB与partB部分使用的是单项,partB与partA部分使用的是双向。并且,2D位置编码通过结合partA和partB合并后的位置编码以及partB的位置编码实现。

位置编码实现代码如下:

# Position ids.
position_ids = torch.arange(seq_length, dtype=torch.long,
                            device=data.device)
position_ids = position_ids.unsqueeze(0).expand_as(data)
if set_loss_mask:
    loss_mask[data == eod_token] = 0.0
# We need to clone as the ids will be modifed based on batch index.
if reset_position_ids:
    position_ids = position_ids.clone()

if reset_position_ids or reset_attention_mask:
    # Loop through the batches:
    for b in range(batch_size):

        # Find indecies where EOD token is.
        eod_index = position_ids[b, data[b] == eod_token]
        # Detach indecies from positions if going to modify positions.
        if reset_position_ids:
            eod_index = eod_index.clone()

        # Loop through EOD indecies:
        prev_index = 0
        for j in range(eod_index.size()[0]):
            i = eod_index[j]
            # Mask attention loss.
            if reset_attention_mask:
                attention_mask[b, 0, (i + 1):, :(i + 1)] = 0
            # Reset positions.
            if reset_position_ids:
                position_ids[b, (i + 1):] -= (i + 1 - prev_index)
                prev_index = i + 1

上面的代码的作用是求取position 1。作者使用了一个for循环来遍历一个批次里的数据,并分别处理每条数据对应的位置id。代码中eod_token应该是一个句子的间隔token,(不过笔者在代码中并没有找到这个参数的定义),作者接着对每个句子的间隔token索引进行遍历,并对对应位置的值进行修改,使其满足上图(b)中的形式。不过,笔者在GLM的源码中好像并没有找到对2d位置编码的定义,不过通过下面代码可以看出作者确实是想使用2d位置编码:

block_position_ids = position_ids[:, 1]
position_ids_ = position_ids[:, 0]

代码中对2d位置编码的embedding表示直接相加,如下所示:

if self.block_position_encoding:
    position_ids, block_position_ids = position_ids[:, 0], position_ids[:, 1]
position_embeddings = self.position_embeddings(position_ids)
hidden_states = hidden_states + position_embeddings
if self.block_position_encoding:
    block_position_embeddings = self.block_position_embeddings(block_position_ids)
    hidden_states = hidden_states + block_position_embeddings

代码中position_embeddings表示position 1block_position_embeddings表示position 2

由于在源码中没有找到2d位置编码的定义,这里给出huggingface上的定义,如下:

#https://huggingface.co/THUDM/chatglm-6b/blob/main/modeling_chatglm.py
def get_position_ids(self, input_ids, mask_positions, device, use_gmasks=None):
    batch_size, seq_length = input_ids.shape
    if use_gmasks is None:
        use_gmasks = [False] * batch_size
    context_lengths = [seq.tolist().index(self.config.bos_token_id) for seq in input_ids]
    if self.position_encoding_2d:
        position_ids = torch.arange(seq_length, dtype=torch.long, device=device).unsqueeze(0).repeat(batch_size, 1)
        for i, context_length in enumerate(context_lengths):
            position_ids[i, context_length:] = mask_positions[i]
        block_position_ids = [torch.cat((
            torch.zeros(context_length, dtype=torch.long, device=device),
            torch.arange(seq_length - context_length, dtype=torch.long, device=device) + 1
        )) for context_length in context_lengths]
        block_position_ids = torch.stack(block_position_ids, dim=0)
        position_ids = torch.stack((position_ids, block_position_ids), dim=1)
    else:
        position_ids = torch.arange(seq_length, dtype=torch.long, device=device).unsqueeze(0).repeat(batch_size, 1)
        for i, context_length in enumerate(context_lengths):
            if not use_gmasks[i]:
                position_ids[i, context_length:] = mask_positions[i]

    return position_ids
2.2 预训练任务

GLM的预训练目标有两个,分别是:

  • Document-level:目标是生成长文本。从原始句子长度的50%-100%的均匀分布中采样span进行遮掩
  • Sentence-level:针对seq2seq任务,预测目标通常是完整的句子或段落。限制被这样的片段必须是完整的句子

在对预训练的目标函数进行设计时,GLM使用下式:

m a x θ E z ∈ Z m [ ∑ i = 1 m l o g p θ ( s z i ∣ x c o r r u p t , s z < i ) ] \underset{\theta}{max}E_{z\in Z_m}[\sum^m_{i=1}logp_{\theta}(s_{z_i}|x_{corrupt},s_{z < i} )] θmaxEzZm[i=1mlogpθ(szixcorrupt,sz<i)]

式中 x c o r r u p t x_{corrupt} xcorrupt表示经过span的文本。

2.3 模型结构

GLM在模型结构上有三个方面的改进,如下:

  • LN与残差连接的位置
  • 将前馈网络变成线性层
  • 用GeLU代替ReLU
2.4 微调

GLM在实现时将NLU的任务转换为生成任务进行实现,换言之,将对应的标签使用[MASK]代替,然后模型生成遮掩的对应的文字。如下图所示,作者定义了一个映射 v ( ˙ ) v(\dot{}) v(˙),该映射可以将标签对应到单词上,如下图的情况就是将“positive”映射为“good”,在以good作为生成的单词实现完形填空。

计算方式如下:

p ( y ∣ x ) = p ( v ( y ) ∣ c ( x ) ) ∑ y ′ ∈ y p ( v ( y ′ ) ∣ c ( x ) ) p(y|x)=\frac{p(v(y)|c(x))}{\sum_{y\prime{} \in y}p(v(y\prime{})|c(x))} p(yx)=yyp(v(y)c(x))p(v(y)c(x))

式中 c ( ˙ ) c(\dot{}) c(˙)表示“cloze”,也就是将输入的文本 x x x转化为完形填空的格式,也就是“{sentence}.it’s really [MASK]”的格式。

2.5 对比

下面介绍GLM和其他已经出现的模型的效果对比。作者给出的对比对象为BERT、XLNetUniLMT5.

BERT

  • 由于MLM的独立性假设,BERT无法捕捉不同[MASK]之间的相互依赖性
  • BERT无法实现对多个token的填充,在被遮掩的token数量为 l l l时,模型需要连续预测 l l l

XLNet

  • 位置编码问题,导致其在推理时需要知道答案的长度
  • 使用双流注意力来防止信息的泄露,但是这会使时间成本翻倍

在XLNet中存在双流注意力机制,分别如上图(a)和(b)所示。其中(a)表示基于内容的注意力机制,与标准的自注意力一致;(b)表示基于query的注意力机制。上图中 h h h表示内容表征,与TRM的隐藏层一样; g g g表示query表征,只有 x x x的上下文信息和位置信息,没有 x x x的信息,并且第一层 g g g的初始化使用随机初始化即可。

下面是XLNet中, g g g进行初始化的代码:

##### Word embedding
word_emb_k, lookup_table = embedding_lookup(
    x=inp_k,
    n_token=n_token,
    d_embed=d_model,
    initializer=initializer,
    use_tpu=use_tpu,
    dtype=tf_float,
    scope='word_embedding')

if inp_q is not None:
  with tf.variable_scope('mask_emb'):
    mask_emb = tf.get_variable('mask_emb', [1, 1, d_model], dtype=tf_float)
    if target_mapping is not None:
      word_emb_q = tf.tile(mask_emb, [tf.shape(target_mapping)[0], bsz, 1])
    else:
      inp_q_ext = inp_q[:, :, None]
      word_emb_q = inp_q_ext * mask_emb + (1 - inp_q_ext) * word_emb_k
output_h = tf.layers.dropout(word_emb_k, dropout, training=is_training)
if inp_q is not None:
  output_g = tf.layers.dropout(word_emb_q, dropout, training=is_training)
with tf.variable_scope('layer_{}'.format(i)):
    if inp_q is not None:
      output_h, output_g = two_stream_rel_attn(
          h=output_h,
          g=output_g,
          r=pos_emb,
          ...)
      reuse = True
    else:
      reuse = False
with tf.variable_scope(scope, reuse=True):
  ##### g-stream
  # query-stream query head
  q_head_g = head_projection(
      g, d_model, n_head, d_head, kernel_initializer, 'q')

T5

  • T5在训练时使用多个“sentinel token”,但是在下游任务时,只需要使用一个

UniLM

  • 仅使用了[MASK]特殊token来代替被遮掩的部分
  • 微调效率低


UniLM合成了多种LM,在进行MLM任务时,每种LM使用到的信息有所区别。left-to-right LM仅使用左侧的信息对遮掩的词进行预测;right-to-left LM仅使用右侧的信息预测;bi LM使用上下文信息;s2s LM则是使用全部的输入和target的左侧信息。

GLM-130B

3.1 介绍

chatGLM就是以GLM作为基础模型训练的得到的大模型。其原始大小为130B(1300亿参数量),经过量化后可达6B(60亿参数量)。大模型一般是指参数量达到千亿级的模型。chatGLM是一个双语模型(中英),并且完全开源。

首先,作者将chatGLM和已有的其他大模型从几个主要方面进行了对比,如下:

表中的SSL表示self-supervised learnin,MIP表示multi-task instruction pre-training,PE表示位置编码,FFN表示前馈网络。根据上表可以总结出chatGLM的几个创新点:

  • 首个使用SSL+MIP的训练方式
  • 使用post-LN的归一化方式,结合deepnorm
  • 使用旋转位置编码RoPE
  • 使用GeGLU代替FFN
  • 使用EGS提高训练稳定性
  • INT4量化
  • 使用faster-trm加速
  • 完全开源
  • 多平台应用
3.2 模型设计

对于掩码方式,chatGLM有两个掩码标记,分别是:

  • [MASK]:主要用来代替句子中的短的span,用于进行NLU任务
  • [gMASK]:用于生成式任务,生成句子末尾随机长度的长span

对于chatGLM-130B,其预训练目标是SSL和MIP的结合, 其中95%的tokens用于SSL训练,5%用于MIP训练,这将有助于提升下游的zero-shot的效果。对于SSL,模型同时使用[MASK]和[gMASK],其中[MASK]用来遮掩30%的连续span,span的长度服从 λ = 3 \lambda=3 λ=3的泊松分布,并且最多为输入句子的15%,另外的70%的tokens使用[gMASK]进行遮掩,长度服从均匀分布。T5等模型的结果显示,MIP任务有助于微调的效果,同时,为了防止MIP破坏大模型的其他能力,仅使用5%的tokens进行MIP。

在chatGLM的实现过程中,deepnorm的设计如下:

D e e p N o r m ( x ) = L N ( α ˙ x + N e t w o r k ( x ) ) DeepNorm(x)=LN(\alpha \dot{} x+Network(x)) DeepNorm(x)=LN(α˙x+Network(x))

式中 α = 2 N 2 \alpha=\sqrt[2]{2N} α=22N ,并且使用Xavier normal initialization初始化方法。

3.3 训练策略

对于[gMASK]的训练目标,最大文本长度设置为2048;对于[MASK]和MIP,最大文本长度设置为512,但是合并4个样本到2048的长度。在前2.5%的样本中,使用warm-up的策略,将batch_size逐渐从192提升到4224;使用AdamW作为优化器,其中 β 1 = 0.9 , β 2 = 0.95 \beta_1=0.9,\beta_2=0.95 β1=0.9,β2=0.95,权重衰减系数设置为0.1;学习率也使用warm-up策略,对前0.5%的样本,学习率从 1 0 − 7 10^{-7} 107逐渐增加到 8 × 1 0 − 5 8\times10^{-5} 8×105,然后使用余弦退火的方式衰减学习率;dropout取值为0.1,使用1.0对梯度进行裁剪。

更完整的配置如下:

为了在保证训练精度的同时,提高训练的稳定性,GLM-130B使用了混合精度实现训练。在训练时,使用FP16用于前向和后向,使用FP32用于优化器和权重。不过这样会出现损失爆炸而训练失败的情况,作者通过几个月的研究,总结出了以下的问题,并提出了改进策略:

(1)当使用pre-LN时,深层的trm参数值可能会变得非常大。通过使用post-LN和deepnorm解决了该问题

(2)随着模型规模的扩大,注意力分数会变得很大,超过FP16的表示范围。作者通过使用EGS(Embedding Layer Gradient Shrink),解决这个问题。
通过实验证明,梯度范数可以对训练崩溃进行有效的衡量,并且,训练崩溃通常滞后于梯度范数的“尖峰”几个训练步骤。通过实验,作者观察到这种尖峰通常是由嵌入层的异常梯度引起的,在GLB-130B的早期训练中,其梯度范数通常比其他层的梯度范数大几个数量级,在早期训练中也会出现剧烈波动,如下图所示:

最后,作者发现梯度收缩的方式有利于改善这种情况,该方法最早在多模态模型 CogView中使用。以 α \alpha α作为收缩因子,该策略可以表示为:

w o r d ‾ e m b = w o r d ‾ e m b ∗ α + w o r d ‾ e m b . d e t a c h ( ) ∗ ( 1 − α ) word\underline{}emb=word\underline{}emb*\alpha+word\underline{}emb.detach()*(1-\alpha) wordemb=wordembα+wordemb.detach()(1α)
在GLM-130B的训练过程中,将 α \alpha α设置为0.1,效果如下图所示:

3.4 量化

量化可以理解为映射,也就是一个取值区间映射到另一个取值区间,进而降低存储模型参数所需的空间大小,而实现整体模型的资源消耗。比如,模型参数的dtype从FP32到FP16,FP16到INT8,以及GLM-130B的FP-16到INT4。

下图为GLM-130B的参数分布与BLOOM-176B的参数分布对比图:

这些参数的分布会直接影响最后量化的效果,分布更广的线性层需要使用更大的bin进行量化,从而导致更多的参数损失。从图中可以看到GPT类型的大模型BLOOM的分布更宽,因此其INT4量化的成功率更低。两者量化的效果对比如下:

GLM-130B各个精度的效果对比如下:

3.5 技术细节

论文中给出了较为详细的技术细节,这里做简单介绍

3.5.1 tokenizer

作者基于icetk包实现文本分词器,该包的词汇量为150000,其中前20000为图像标记,后续为文本标记,其文本分词器由sentencepiece在25GB的双语预料上训练的。作者在此基础上对分词器进行了调整。

将分词器分为4个成分,通用的tokens处于No.20000到No.20099,主要是一些标点符号、数字和空格;No.20100到No.83822为英文标记;No.83823到No.145653为中文标记;后续的标记为特殊符号,包括标点符号和其他语言的片段。

在固有标记的基础上,添加了[MASK]和[gMASK]用于模型预测,[sop]、[eop]和[eos]用于分隔句子和段落。

3.5.2 位置编码和FFN

在GLM-130B的实现中,使用的是旋转位置编码,这是一种以绝对位置编码形式实现的相对位置编码,这一点与GLM中的二维位置编码不同。其核心如下:

( R m q ) T ( R n k ) = q T R m T R n k = q T R n − m k (R_mq)^T(R_nk)=q^TR_m^TR_nk=q^TR_{n-m}k (Rmq)T(Rnk)=qTRmTRnk=qTRnmk

式中 R R R的定义如下:


为了让它的值随着距离的增加而衰减,将 θ \theta θ设置如下:

θ = { θ i = 1000 0 − 2 ( i − 1 ) d i ∈ [ 1 , 2 , . . . , d 2 ] } \theta=\{\theta_i=10000^{\frac{-2(i-1)}{d}}i\in [1,2,...,\frac{d}{2}]\} θ={θi=10000d2(i1)i[1,2,...,2d]}

这里用到的旋转位置编码是一维的,作者通过以下方式对GLM的二维进行转化:

  • 对于短span,直接去掉第二维
  • 对于末尾长span,从上下文标记 s − 1 s-1 s1开始延长第一维的位置编码

另外,作者将FNN进行了替换,使用带有GeLU的GLU对其替换:

F F N G e G L U ( x ; W 1 , V , W 2 ) = ( G e L U ( x W 1 ) ⨂ x V ) W 2 FFN_{GeGLU}(x;W_1,V,W_2)=(GeLU(xW_1)\bigotimes xV)W_2 FFNGeGLU(x;W1,V,W2)=(GeLU(xW1)xV)W2

为了保持参数量的一致,原本FFN层的 4 × 4\times 4×降低到 8 3 × \frac{8}{3}\times 38×

3.5.3 量化

在量化过程中,GLM-130B主要使用了两种量化方式进行实验。

(1)Absmax Quantization

一种对称量化,将 x x x的范围从 [ − a b s m a x ( x ) , a b s m a x ( x ) ] [-absmax(x),absmax(x)] [absmax(x),absmax(x)]映射到 [ − ( 2 b − 1 ) , 2 b − 1 ] [-(2^b-1),2^b-1] [(2b1),2b1]的范围:

s x = a b s m a x ( x ) 2 b − 1 − 1 s_x=\frac{absmax(x)}{2^{b-1}-1} sx=2b11absmax(x)
x q = r o u n d ( x s x ) x_q=round(\frac{x}{s_x}) xq=round(sxx)
式中, s x s_x sx表示缩放因子, b b b表示bit width

(2)Zeropoint Quantization

这是一种非对称量化,将 x x x [ m i n ( x ) , m a x ( x ) ] [min(x),max(x)] [min(x),max(x)]映射到 [ − ( 2 b − 1 ) , 2 b − 1 ] [-(2^b-1),2^b-1] [(2b1),2b1],如下:

s x = m a x ( x ) − m i n ( x ) 2 b − 2 s_x=\frac{max(x)-min(x)}{2^b-2} sx=2b2max(x)min(x)
z x = r o u n d ( m i n ( x ) s x ) + 2 b − 1 − 1 z_x=round(\frac{min(x)}{s_x})+2^{b-1}-1 zx=round(sxmin(x))+2b11
x q = r o u n d ( x s x ) − z x x_q=round(\frac{x}{s_x})-z_x xq=round(sxx)zx

(3)Col/Row-wise Quantization

对权重矩阵使用单个缩放因子通常会导致更大的量化误差,一个异常值会降低所有元素的精度,因此常用按行或按列对权重矩阵进行分组,每组单独量化,且具有独立的缩放因子。

量化实验结果对比如下:

GLM-130B最后使用的是Absmax量化方法,相比于Zeropoint,在可以模型性能的同时有着更高的计算效率。

总结

以上就是对GLM和GLM-130B的一些较为基础的介绍,通过对GLM-130B论文的阅读,可以发现大模型更加注重的是如何训练,论文中的很多策略也都是为了提高稳定性,同时也提出了很多用于节约资源的并行策略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值