1.llm_aigc

下载本文pdf:https://github.com/daiwk/collections/blob/master/pdfs/llm_aigc.pdf

各种学习相关代码

https://github.com/daiwk/llms_new

从word2v到Transformer

LSTM

超生动图解LSTM和GRU,一文读懂循环神经网络!

fasttext&word2vec

注:w2v训练时的内积不是2个emb-in的内积,而是emb-in和emb-out的内积

fasttext源码解析

  • Dictionary::readWord:空格分割,一次读出来一个word

  • Dictionary::add:每个word求个hash,加进词典时,id就是从0开始的序号,同时记录一下词频

  • Dictionary::threshold:按词频排序,扔掉低频词

  • Dictionary::initNgrams:每个词,加上前缀BOW(<)和后缀(>),然后先扔进这个词的subwords里,然后再调用 Dictionary::computeSubwords把这个词的ngrams也扔进它的subwords里

整个词表,是word数+bucket这么大,其中bucket表示可容纳的subwords和wordNgrams的数量,默认200w

为什么Word2Vec训练中, 需要对负采样权重开3/4次幂?

Distributed Representations of Words and Phrases and their Compositionality里提到

通过对权重开3/4次幂,可以提升低频词被抽到的概率。在保证高频词容易被抽到的大方向下,通过权重3/4次幂的方式,适当提升低频词、罕见词被抽到的概率。如果不这么做,低频词,罕见词很难被抽到,以至于不被更新到对应的Embedding。

BPE/WordPiece分词

【Subword】深入理解NLP Subword算法:BPE、WordPiece、ULM

Transformer原理

从三大顶会论文看百变Self-Attention

包学包会,这些动图和代码让你一次读懂「自注意力」

http://jalammar.github.io/illustrated-transformer/

从熵不变性看Attention的Scale操作

Transformers Assemble(PART I) 讲了3篇

Transformers Assemble(PART II) 又讲了三篇

为什么层次化softmax没人用了

Transformer 结构中最后一层 softmax 为什么不再使用 层次化softmax了呢?

主要还是计算资源的问题。

Mikolov发明word2vec的几个版本大概在13-14年前后。那个时候GPU非常少见,印象里面CMU的NLP组没有GPU,Stanford NLP lab只有6块K40。

大规模直接算softmax是在google的14年那篇seq2seq做MT的文章。为了快,把一个softmax并行在4块GPU上,每个GPU负责四分之一。那个年代,大多数NLP组全组都不会有4块GPU。

hierarchical softmax是softmax的近似,suboptimal的。当如今计算资源足够大的时候,当然包括时间和显存 (BERT 和 Elmo 都没有用hierarchical),hierarchical softmax就逐渐退出了历史舞台。

Transformer会不会规划未来

Transformer本可以深谋远虑,但就是不做

Do Language Models Plan for Future Tokens?

在训练期间的梯度既会为当前token位置的损失优化权重,也会为该序列后面的token进行优化,那么这二者会以怎样的比例分配资源?

  • 预缓存假设(pre-caching hypothesis):在时间步tt计算与当前时间步的推理任务无关可能对未来时间步t+τt + \tau有用的特征

  • 面包屑假设(breadcrumbs hypothesis):与时间步tt最相关的特征已经等同于将在时间步t+τt + \tau最有用的特征。

设计了一种合成场景,其中只能通过显式的预缓存完成任务,即模型必须为下一token预先计算信息,**否则就无法在一次单向通过中准确计算出正确答案。**发现明显的证据说明transformer可以学习预缓存,即当必须预计算信息来最小化损失时,它们就会这样做。

但在真实语言数据上,语言模型并不会显著地准备用于未来的信息。相反,它们是计算对预测下一个token有用的特征——事实证明这对未来的步骤也很有用

Transformer的FLOPS和访存带宽

https://zhuanlan.zhihu.com/p/624740065

AA的shape是m×km\times kBB的shape是k×nk\times n,那么矩阵乘法ABAB需要m×k×nm\times k\times n次的乘法,也需要同样多次的加法,所以FLOPS是2×m×k×n2\times m\times k\times n

假设batchsize是bb,序列长度ss,原来的emb是dd,即输入的是[b,s,d][b,s,d],一般d=dk=dv=dqd=d_k=d_v=d_qWQW_QWKW_KWVW_V都是dv×dvd_v\times d_v,对应的Q、K、V矩阵都是s×dvs\times d_v,有head_numhead\_num个头,每个头的维度per_head_d=dhead_numper\_head\_d=\frac{d}{head\_num}

attention的FLOPS

attention的公式:

Q=xWQ,K=xWK,V=xWVxout =softmax(QKTh)VWo+x\begin{aligned} &Q=x W_Q, K=x W_K, V=x W_V\\ &x_{\text {out }}=\operatorname{softmax}\left(\frac{Q K^T}{\sqrt{h}}\right) \cdot V \cdot W_o+x \end{aligned}
  • 计算3个Q、K、V:要算三次s×ds\times dd×dvd\times d_v的矩阵乘法,所以是:3×2×b×s×d×dv3\times 2\times b\times s\times d\times d_v

    • 输入:[b,s,d][b, s, d]和3个[b,d,dv][b, d, d_v]

    • 输出:[b,s,dv][b, s, d_v],再把最后一维dvd_v拆成head_numhead\_num份,再把中间两维互换一下,得到[b,head_num,s,per_head_d][b, head\_num, s, per\_head\_d]

  • 计算Q和K的相似度:要算一次s×dvs\times d_vdv×sd_v\times s的矩阵乘法,2×b×s2×dk2\times b\times s^2\times d_k

    • 输入:[b,head_num,s,per_head_d][b, head\_num, s, per\_head\_d][b,head_num,per_head_d,s][b, head\_num, per\_head\_d, s]

    • 输出:[b,head_num,s,s][b, head\_num, s, s]

  • 把相似度用到V上:要算一次s×ss\times ss×dvs\times d_v的矩阵乘法,,2×b×s2×dv2\times b\times s^2 \times d_v

    • 输入:[b,head_num,s,s][b, head\_num, s, s][b,head_num,s,per_head_d][b, head\_num, s, per\_head\_d]

    • 输出:[b,head_num,s,per_head_d][b, head\_num, s, per\_head\_d]

  • 最后过一个线性映射:要算一次s×dvs\times d_v的和dv×dvd_v\times d_v的矩阵乘法,2×b×s×dv×dv2\times b\times s\times d_v\times d_v

    • 输入:[b,s,dv][b, s, d_v][dv,dv][d_v, d_v]

    • 输出:[b,s,dv][b, s, d_v]

因为dk=dv=dq=dd_k=d_v=d_q=d,单纯计算attention总共就是8bsd2+4bs2d8bsd^2 + 4bs^2d

FFN的FLOPS

FFN的公式:

x=fgelu (xout W1)W2+xout x=f_{\text {gelu }}\left(x_{\text {out }} W_1\right) W_2+x_{\text {out }}

在原始Transformer中,W1W_1的shape是[d,4d][d,4d]W2W_2的shape是[4d,d][4d,d]

  • 第一个线性层:2×b×s× d×4d=8×b×s× d22\times b\times s\times\ d\times 4d=8\times b\times s\times\ d^2

    • 输入:[b,s,d][b, s, d][d,4d][d,4d]

    • 输出:[b,s,4d][b, s, 4d]

  • 第二个线性层:2×b×s× 4d×d=8×b×s× d22\times b\times s\times\ 4d\times d=8\times b\times s\times\ d^2

    • 输入:[b,s,4d][b, s, 4d][4d,d][4d,d]

    • 输出:[b,s,d][b, s, d]

所以一层Transformer,即attention+FFN的计算量为(8bsd2+4bs2d)+16bsd2=24bsd2+4bs2d(8bsd^2 + 4bs^2d)+16bsd^2=24bsd^2+4bs^2d

有两点需要注意的:

  • 对NLP任务来讲,一般dd是个比较固定的值,如512,而ss变大,效果会更好,所以一般是s>ds>d,所以复杂度取决于ss的大小。

  • 但有些模型的初始设置不是这样的,例如GPT3的175B模型里,s=2048,d=12288s=2048,d=12288,当然,算力够的话也可以把ss变大

自己感觉:既然是24bsd2+4bs2d24bsd^2+4bs^2d,其实就是6sd26sd^2s2ds^2d的大小,即6d6dss的大小,如果6d>s6d>s,则d2d^2起主导,反之s2s^2起主导

DIN的FLOPS

特殊地,对于推荐中的DIN那种,看当前item和历史s个item的相关性,即q的序列长度只有1,不考虑多头,而这其实也是decoder预测下一个词时过一层Transformer的复杂度

已经有3个序列长度为s1s-1的QKV的cache,要算第ss个词和这s1s-1个词的attention

  • 计算第ss个词的3个Q、K、V:要算三次$1\times d$和d×dvd\times d_v的矩阵乘法,所以是:3×2×b×1×d×dv3\times 2\times b\times 1\times d\times d_v

    • 输入:[b,1,d][b, 1, d]和3个[b,d,dv][b, d, d_v]

    • 输出:[b,1,dv][b, 1, d_v]

  • 计算Q和K的相似度:要算一次1×dv1\times d_vdv×sd_v\times s的矩阵乘法,2×b×1×dk×s2\times b\times 1\times d_k\times s【这里的K是历史s1s-1长度的序列拼上当前词,当然对DIN来讲要去掉当前词,这里先忽略这个】

    • 输入:[b,1,dv][b, 1, d_v][b,dv,s][b, d_v, s]

    • 输出:[b,1,s][b, 1, s]

  • 把相似度用到V上:要算一次1×s1\times ss×dvs\times d_v的矩阵乘法,,2×b×1×dv×s2\times b\times 1 \times d_v \times s【同样地,这里的V是历史s1s-1长度的序列拼上当前词,当然对DIN来讲要去掉当前词,这里先忽略这个】

    • 输入:[b,1,s][b, 1, s][b,s,dv][b, s, d_v]

    • 输出:[b,1,dv][b, 1, d_v]

  • 最后过一个线性映射:要算一次1×dv1\times d_v的和dv×dvd_v\times d_v的矩阵乘法,2×b×1×dv×dv2\times b\times 1\times d_v\times d_v

    • 输入:[b,1,dv][b, 1, d_v][dv,dv][d_v, d_v]

    • 输出:[b,1,dv][b, 1, d_v]

  • 第一个线性层:2×b×1× d×4d=8×b×1× d22\times b\times 1\times\ d\times 4d=8\times b\times 1\times\ d^2

    • 输入:[b,1,d][b, 1, d][d,4d][d,4d]

    • 输出:[b,1,4d][b, 1, 4d]

  • 第二个线性层:2×b×1× 4d×d=8×b×1× d22\times b\times 1\times\ 4d\times d=8\times b\times 1\times\ d^2

    • 输入:[b,1,4d][b, 1, 4d][4d,d][4d,d]

    • 输出:[b,1,d][b, 1, d]

总共是6bd2+2bds+2bds+2bd2+8bd2+8bd2=24bd2+4bds6bd^2+2bds+2bds+2bd^2+8bd^2+8bd^2=24bd^2+4bds

Transformer的访存

GPU架构的介绍参考https://developer.nvidia.com/blog/nvidia-ampere-architecture-in-depth/https://www.zhihu.com/question/319355296/answer/2193938981,GPU对比CPU如下:

  • 任务模式

    • CPU由专为顺序串行处理而优化的几个核心组成

    • GPU则拥有一个由数以千计的更小、更高效的核心(专为同时处理多重任务而设计)组成的大规模并行计算架构。同时CPU相当的一部分时间在执行外设的中断、进程的切换等任务,而GPU有更多的时间并行计算。

  • 功能定位

    • CPU不但要承担计算任务还有承担逻辑控制等任务。

    • GPU在渲染画面时需要同时渲染数以百万记的顶点或三角形,故GPU的设计是可以充分支持并行计算。

  • 系统集成

    • GPU作为一类外插设备,在尺寸、功率、散热、兼容性等方面的限制远远小于CPU,这样可以让GPU有较大的显存和带宽。

以A100为例,整体架构如下

  1. PCIE层:通过PCIE接口以外设的方式集成到服务器上。

  2. 中间一坨绿色的部分是GPU的计算核心SM(Streaming Multiprocessor),在A100中,一个SM有64个用于计算的Core,共108个SM(图里是GA100,有128个SM),故共6192个Core。

  3. 中间蓝色部分是L2缓存

  4. NVLink:多个GPU间进行通信的组件,会优化GPU间的通信,提升传输效率。

  5. 两侧的HBM2是显存,目前的A100的显存有两种40G and 80G

A100的SM如图所示,

GPU的显存分成两部分:

  • Global Memory:整体架构图中的两侧HBM2部分,例如A100 80G就有80G的global memory,2TB/s带宽,访问速度比较慢

  • Shared Memory:SM图中浅蓝色的L1 Data Cache,例如A100中每个SM中有192KB,访问速度比较快

从图中可见,A100的FP16312T的FLOPS

以矩阵乘法为例,[M,K]×[K,N]>[M,N][M, K] \times [K, N] -> [M,N]

  • 计算时间:2MKN/FLOPS2MKN/FLOPS

  • 访存时间为:(MN+MK+KN)/memory_bandwidth(MN+MK+KN)/memory\_bandwidth,因为首先要读取MKMKNKNK这两个矩阵,然后结果还要写入MNMN这个矩阵里。假设是fp16,占2bytes,那就还要乘以2

假设b=1,s=4096,d=dk=2048b=1,s=4096,d=d_k=2048,以计算Q和K的相似度为例,对比一下训练和预测时的计算耗时访存耗时

  • 训练时M=4096,N=2048,K=4096M=4096,N=2048,K=4096==>计算是瓶颈

    • FLOPS:2×b×s2×dk=2×1×40962×2048=687194767362\times b\times s^2\times d_k=2\times 1\times 4096^2\times 2048=68719476736

    • 计算耗时:FLOPS/max_FLOPS=68719476736/(312×1012)=0.00022s=220×106s=220usFLOPS/max\_FLOPS=68719476736/(312\times 10^{12})=0.00022s=220\times 10^{-6}s=220us

    • 访存耗时:(MN+MK+KN)/memory_bandwidth=2×(4096×2048+4096×4096+4096×2048)/(2×1012)=3.35544×105s=33.544×106=33.5544us(MN+MK+KN)/memory\_bandwidth=2\times (4096\times 2048+4096\times 4096+4096\times 2048)/(2\times 10^{12})=3.35544\times 10^{-5}s=33.544\times 10^{-6}=33.5544us

  • 预测时M=1,N=2048,K=4096M=1,N=2048,K=4096==>访存是瓶颈

    • FLOPS:2×b×1×dk×s=2×1×2048×4096=167772162\times b\times 1\times d_k\times s=2\times 1\times 2048\times 4096=16777216

    • 计算耗时:FLOPS/max_FLOPS=16777216/(312×1012)=5.38×108s=0.0538×106s=0.0538usFLOPS/max\_FLOPS=16777216/(312\times 10^{12})=5.38\times 10^{-8}s=0.0538\times 10^{-6}s=0.0538us

    • 访存耗时:(MN+MK+KN)/memory_bandwidth=2×(1×2048+1×4096+4096×2048)/(2×1012)=8.3948×106s=8.3948us(MN+MK+KN)/memory\_bandwidth=2\times (1\times 2048+1\times 4096+4096\times 2048)/(2\times 10^{12})=8.3948\times 10^{-6}s=8.3948us

一些常见的名词:

  • H2D:host to device,从cpu拷贝到gpu

  • D2H:device to host,从gpu拷贝到cpu

PLM:仅编码器/仅解码器/编码器+解码器

仅编码器的BERT

BERT小学生级上手教程,从原理到上手全有图示,还能直接在线运行

BERT源码分析(PART I)

BERT源码分析(PART II)

Dive into BERT:语言模型与知识

关于BERT,面试官们都怎么问

主要讲了下面3篇:

Language Models as Knowledge Bases?

Linguistic Knowledge and Transferability of Contextual Representations

What does BERT learn about the structure of language?

A Primer in BERTology: What we know about how BERT works

摘要:目前,基于 Transformer 的模型已经广泛应用于自然语言处理中,但我们依然对这些模型的内部工作机制知之甚少。在本文中,来自麻省大学洛威尔分校的研究者对流行的 BERT 模型进行综述,并综合分析了 40 多项分析研究。他们还概览了对模型和训练机制提出的改进,然后描画了未来的研究方向。

Pre-trained Models for Natural Language Processing: A Survey

BERT系列文章汇总导读

ALBERT、XLNet,NLP技术发展太快,如何才能跟得上节奏?

绝对干货!NLP预训练模型:从transformer到albert

ALBERT一作蓝振忠:预训练模型应用已成熟,ChineseGLUE要对标GLUE基准

有哪些令你印象深刻的魔改Transformer?

BERT模型超酷炫,上手又太难?请查收这份BERT快速入门指南!

multi-head att 实现

输入原始的query(即from_tensor)之后, 把[batch, from_seq, emb]变成[?, emb],其中?=batch*from_seq

from_tensor_2d = reshape_to_matrix(from_tensor)

def reshape_to_matrix(input_tensor):
    """Reshapes a >= rank 2 tensor to a rank 2 tensor (i.e., a matrix)."""
    ndims = input_tensor.shape.ndims
    if ndims < 2:
        raise ValueError("Input tensor must have at least rank 2. Shape = %s" %
            (input_tensor.shape))
    if ndims == 2:
        return input_tensor
    width = input_tensor.shape[-1]
output_tensor = tf.reshape(input_tensor, [-1, width])
    return output_tensor

然后再接一个fc,把[?, emb]变成[?, head_num * per_head],一般head_num * per_head=emb

query_layer = tf.layers.dense(
        from_tensor_2d,
        num_attention_heads * size_per_head,
        activation=query_act,
        name="query",
        kernel_initializer=create_initializer(initializer_range))

因为?=batch*from_seq,所以可以直接做如下变换

query_layer = transpose_for_scores(query_layer, batch_size,
        num_attention_heads, from_seq_length,
        size_per_head)

实际就是把?拆开成batch, from_seq,整个变成[batch, from_seq, head_num, per_head],然后做了个 transpose,把1和2互换了下,得到[batch, head_num, from_seq, per_head]

def transpose_for_scores(input_tensor, batch_size, num_attention_heads,
        seq_length, width):
    output_tensor = tf.reshape(
            input_tensor, [batch_size, seq_length, num_attention_heads, width])

    output_tensor = tf.transpose(output_tensor, [0, 2, 1, 3])
    return output_tensor

然后key也做完全一样的操作(不过处理的是to_tensor,如果是self-attention,那to_tensor=from_tensor), 得到[batch, head_num, to_seq, per_head]

to_tensor_2d = reshape_to_matrix(to_tensor)
key_layer = tf.layers.dense(
        to_tensor_2d,
        num_attention_heads * size_per_head,
        activation=key_act,
        name="key",
        kernel_initializer=create_initializer(initializer_range))

key_layer = transpose_for_scores(key_layer, batch_size, num_attention_heads,
        to_seq_length, size_per_head)

然后就算QKTQK^T了,注意这里对key取了转置,也就是[batch, head_num, from_seq, per_head]乘以[batch, head_num, per_head, to_seq],得到的结果是[batch, head_num, from_seq, to_seq]

attention_scores = tf.matmul(query_layer, key_layer, transpose_b=True)
attention_scores = tf.multiply(attention_scores,
    1.0 / math.sqrt(float(size_per_head)))

if attention_mask is not None:
    # `attention_mask` = [B, 1, F, T]
    attention_mask = tf.expand_dims(attention_mask, axis=[1])

    # Since attention_mask is 1.0 for positions we want to attend and 0.0 for
    # masked positions, this operation will create a tensor which is 0.0 for
    # positions we want to attend and -10000.0 for masked positions.
    adder = (1.0 - tf.cast(attention_mask, tf.float32)) * -10000.0

    # Since we are adding it to the raw scores before the softmax, this is
    # effectively the same as removing these entirely.
    attention_scores += adder
attention_probs = tf.nn.softmax(attention_scores)
attention_probs = dropout(attention_probs, attention_probs_dropout_prob)

然后看下value的操作:

value_layer = tf.layers.dense(
        to_tensor_2d,
        num_attention_heads * size_per_head,
        activation=value_act,
        name="value",
        kernel_initializer=create_initializer(initializer_range))

# `value_layer` = [batch, to_seq, head_num, per_head]
value_layer = tf.reshape(
            value_layer,
            [batch_size, to_seq_length, num_attention_heads, size_per_head])

# `value_layer` = [batch, head_num, to_seq, per_head]
value_layer = tf.transpose(value_layer, [0, 2, 1, 3])

# `context_layer` = [batch, head_num, from_seq, per_head]
context_layer = tf.matmul(attention_probs, value_layer)

# `context_layer` = [batch, from_seq, head_num, per_head]
context_layer = tf.transpose(context_layer, [0, 2, 1, 3])

再确认一点,softmax(QKT)softmax(QK^T)[batch, head_num, from_seq, to_seq],而VV[batch, head_num, to_seq, per_head],所以context_layer是[batch, head_num, from_seq, per_head]

最后,再搞一下,变回[batch, from_seq, head_num * per_head]

if do_return_2d_tensor:
# `context_layer` = [B*F, N*H]
    context_layer = tf.reshape(
        context_layer,
        [batch_size * from_seq_length, num_attention_heads * size_per_head])
else:
# `context_layer` = [B, F, N*H]
    context_layer = tf.reshape(
        context_layer,
        [batch_size, from_seq_length, num_attention_heads * size_per_head])

如上过程是Concat(head1,...,headh)Concat(head_1, ..., head_h),其中headi=Attention(QWiQ,KWiK,VWiV)head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)。包装在了函数attention_layer之中,我们注意到原文还有一个大小为hdv×dmodelhd_v \times d_{model}WOW^O,也就是大小为dmodel×dmodeld_{model}\times d_{model},再看看源码。。也就是说,正常的bert里,attention_heads就只有一个元素,然后接了个hidden_size的fc,而前面的代码里也提到了hidden_size正好就是dmodeld_{model},所以这就是WOW^O

attention_heads = []
with tf.variable_scope("self"):
    attention_head = attention_layer(xxxxx)
    attention_heads.append(attention_head)
    attention_output = None
    if len(attention_heads) == 1:
        attention_output = attention_heads[0]
    else:
        # In the case where we have other sequences, we just concatenate
        # them to the self-attention head before the projection.
        attention_output = tf.concat(attention_heads, axis=-1)
    # Run a linear projection of `hidden_size` then add a residual
    # with `layer_input`.
    with tf.variable_scope("output"):
        attention_output = tf.layers.dense(
            attention_output,
            hidden_size,
            kernel_initializer=create_initializer(initializer_range))
        attention_output = dropout(attention_output, hidden_dropout_prob)
        attention_output = layer_norm(attention_output + layer_input)

关于 mask,可以看看这个https://juejin.im/post/5b9f1af0e51d450e425eb32d

摘抄一下:

什么是padding mask呢?回想一下,我们的每个批次输入序列长度是不一样的!也就是说,我们要对输入序列进行对齐!具体来说,就是给在较短的序列后面填充0。因为这些填充的位置,其实是没什么意义的,所以我们的attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。 具体的做法是,把这些位置的值加上一个非常大的负数(可以是负无穷),这样的话,经过softmax,这些位置的概率就会接近0!

而sequence mask是为了使得decoder不能看见未来的信息。也就是对于一个序列,在time_step为t的时刻,我们的解码输出应该只能依赖于t时刻之前的输出,而不能依赖t之后的输出。因此我们需要想一个办法,把t之后的信息给隐藏起来。 那么具体怎么做呢?也很简单:产生一个上三角矩阵,上三角的值全为1,下三角的值权威0,对角线也是0。把这个矩阵作用在每一个序列上,就可以达到我们的目的啦。

masked-language-model的实现

https://github.com/google-research/bert/blob/eedf5716ce1268e56f0a50264a88cafad334ac61/run_pretraining.py#L240

如下,其中hidden_size就是是dmodeld_{model}

def get_masked_lm_output(bert_config, input_tensor, output_weights, positions,
                         label_ids, label_weights):
  """Get loss and log probs for the masked LM."""
  input_tensor = gather_indexes(input_tensor, positions)

  with tf.variable_scope("cls/predictions"):
    # We apply one more non-linear transformation before the output layer.
    # This matrix is not used after pre-training.
    with tf.variable_scope("transform"):
      input_tensor = tf.layers.dense(
          input_tensor,
          units=bert_config.hidden_size,
          activation=modeling.get_activation(bert_config.hidden_act),
          kernel_initializer=modeling.create_initializer(
              bert_config.initializer_range))
      input_tensor = modeling.layer_norm(input_tensor)

    # The output weights are the same as the input embeddings, but there is
    # an output-only bias for each token.
    output_bias = tf.get_variable(
        "output_bias",
        shape=[bert_config.vocab_size],
        initializer=tf.zeros_initializer())
    logits = tf.matmul(input_tensor, output_weights, transpose_b=True)
    logits = tf.nn.bias_add(logits, output_bias)
    log_probs = tf.nn.log_softmax(logits, axis=-1)

    label_ids = tf.reshape(label_ids, [-1])
    label_weights = tf.reshape(label_weights, [-1])

    one_hot_labels = tf.one_hot(
        label_ids, depth=bert_config.vocab_size, dtype=tf.float32)

    # The `positions` tensor might be zero-padded (if the sequence is too
    # short to have the maximum number of predictions). The `label_weights`
    # tensor has a value of 1.0 for every real prediction and 0.0 for the
    # padding predictions.
    per_example_loss = -tf.reduce_sum(log_probs * one_hot_labels, axis=[-1])
    numerator = tf.reduce_sum(label_weights * per_example_loss)
    denominator = tf.reduce_sum(label_weights) + 1e-5
    loss = numerator / denominator

  return (loss, per_example_loss, log_probs)

其中的gather如下:

def gather_indexes(sequence_tensor, positions):
  """Gathers the vectors at the specific positions over a minibatch."""
  sequence_shape = modeling.get_shape_list(sequence_tensor, expected_rank=3)
  batch_size = sequence_shape[0]
  seq_length = sequence_shape[1]
  width = sequence_shape[2]

  flat_offsets = tf.reshape(
      tf.range(0, batch_size, dtype=tf.int32) * seq_length, [-1, 1])
  flat_positions = tf.reshape(positions + flat_offsets, [-1])
  flat_sequence_tensor = tf.reshape(sequence_tensor,
                                    [batch_size * seq_length, width])
  output_tensor = tf.gather(flat_sequence_tensor, flat_positions)
  return output_tensor

注意调用时传的是如下参数

    (masked_lm_loss,
     masked_lm_example_loss, masked_lm_log_probs) = get_masked_lm_output(
         bert_config, model.get_sequence_output(), model.get_embedding_table(),
         masked_lm_positions, masked_lm_ids, masked_lm_weights)

BERT的可解释性

ACL 2019 | 理解 BERT 每一层都学到了什么

What does BERT learn about the structure of language?

探索BERT深层次的表征学习是一个非常有必要的事情,一是这可以帮助我们更加清晰地认识BERT的局限性,从而改进BERT或者搞清楚它的应用范围;二是这有助于探索BERT的可解释性

更复杂的BERT

站在BERT肩膀上的NLP新秀们(PART III)

BERT时代与后时代的NLP

美团BERT的探索和实践

Bert时代的创新(应用篇):Bert在NLP各领域的应用进展

中文BERT

WWM

哈工大讯飞联合实验室发布基于全词覆盖的中文BERT预训练模型

https://github.com/ymcui/Chinese-BERT-wwm

论文:Pre-Training with Whole Word Masking for Chinese BERT

ERNIE

参考中文任务全面超越BERT:百度正式发布NLP预训练模型ERNIE

ERNIE: Enhanced Representation through Knowledge Integration

使用entity-level masking和phrase-level masking两种mask方法

输入的每个样本由5个 ';' 分隔的字段组成,数据格式:

  • token_ids

  • sentence_type_ids:两句话,第一句都是0,第二句都是1

  • position_ids

  • seg_labels:分词边界信息: 0表示词首、1表示非词首、-1为占位符, 其对应的词为 CLS 或者 SEP;

  • next_sentence_label

例如:

1 1048 492 1333 1361 1051 326 2508 5 1803 1827 98 164 133 2777 2696 983 121 4 19 9 634 551 844 85 14 2476 1895 33 13 983 121 23 7 1093 24 46 660 12043 2 1263 6 328 33 121 126 398 276 315 5 63 44 35 25 12043 2;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1;0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55;-1 0 0 0 0 1 0 1 0 0 1 0 0 1 0 1 0 0 0 0 0 0 1 0 1 0 0 1 0 1 0 0 0 0 1 0 0 0 0 -1 0 0 0 1 0 0 1 0 1 0 0 1 0 1 0 -1;0

和bert在mask上的区别:

一个句子的不同level的mask方式:

ERNIE 2.0: A CONTINUAL PRE-TRAINING FRAMEWORK FOR LANGUAGE UNDERSTANDING

跨语言

XLM

Massively Multilingual Sentence Embeddings for Zero-Shot Cross-Lingual Transfer and Beyond,XLM的主要思想还是来自于这篇文章,借用了BERT的框架最后成了XLM。本文提出了LASER(Language-Agnostic SEntence Representations)

XLM:facebook提出Cross-lingual Language Model Pretraining,加了language emb

  • 无监督的方法:只依赖单语种数据(monolingual data)

  • 有监督的方法:对平行语料使用新的跨语言loss

Facebook最新语言模型XLM-R:多项任务刷新SOTA,超越单语BERT

XLM-R

Unsupervised Cross-lingual Representation Learning at Scale

来自facebook。针对多种跨语言的传输任务,大规模地对多语言语言模型进行预训练可以显著提高性能。在使用超过 2TB 的已过滤 CommonCrawl 数据的基础上,研究者在 100 种语言上训练了基于 Transformer 的掩模语言模型。该模型被称为 XLM-R,在各种跨语言基准测试中,其性能显著优于多语言 BERT(mBERT),其中 XNLI 的平均准确度为+ 13.8%,MLQA 的平均 F1 得分为+ 12.3%,而 FQ 的平均 F1 得分为+ 2.1% NER。XLM-R 在低资源语言上表现特别出色,与以前的 XLM 模型相比,斯瓦希里语(Swahili)的 XNLI 准确性提升了 11.8%,乌尔都语(Urdu)的准确性提升了 9.2%。研究者还对获得这些提升所需的关键因素进行了详细的实证评估,包括(1)积极转移和能力稀释;(2)大规模资源资源的高低性能之间的权衡。最后,他们首次展示了在不牺牲每种语言性能的情况下进行多语言建模的可能性。XLM-Ris 在 GLUE 和 XNLI 基准测试中具有强大的单语言模型,因此非常具有竞争力。

更长序列

transformer-xl

Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context

最简单的处理长文本方法:对长文本直接切成多个segment,每个segment独立过transformer,segment间是没有信息流动的

transformer-xl的处理方法:参考RNN的隐藏记忆单元,对上一个segment计算的隐藏状态序列进行fixed和cached,并在模型处理下一个新的segment时将其缓存为可重用的扩展上下文

如图,第k+1层的第i个元素,用到了第k层的第i-1, i-2, ...,i - segment_len+1这几个元素,所以对于最顶层的来讲,实际看到的窗口就更长了。为此还提出了相对位置编码,即使用两个token的相对距离代替之前的绝对位置。

此外,Stabilizing Transformers for Reinforcement Learning说明了为什么标准的transformer架构很难在强化学习中优化。研究者同时提出了一种架构Gated Transformer-XL(GTrXL),可以很好地提升transformer架构和变体的稳定性,并加速学习,可以超过LSTM,在多任务学习 DMLab-30 基准上达到 SOTA 的水平。

XLNet

XLNet : 运行机制及和 Bert 的异同比较

Transformer-XL与XLNet笔记

什么是XLNet中的双流自注意力

20项任务全面碾压BERT,CMU全新XLNet预训练模型屠榜(已开源)

参考拆解XLNet模型设计,回顾语言表征学习的思想演进

他们创造了横扫NLP的XLNet:专访CMU博士杨植麟

XLNet: Generalized Autoregressive Pretraining for Language Understanding

代码:https://github.com/zihangdai/xlnet

  • 自回归(autoregressive, AR):主要的论文有这几篇:Semi-supervised sequence learningDeep contextualized word representationsImproving language understanding by generative pre-training。通过一个autoregressive的模型来估计文本语料库的概率分布。也就是给定一个文本序列x=(x1,,xT)\mathbf{x}=\left(x_{1}, \cdots, x_{T}\right),AR将likelihood因式分解(factorize)成一个前向的乘积p(x)=t=1Tp(xtx<t)p(\mathbf{x})=\prod_{t=1}^{T} p\left(x_{t} | \mathbf{x}_{<t}\right),或者是一个后向的乘积p(x)=t=T1p(xtx>t)p(\mathbf{x})=\prod_{t=T}^{1} p\left(x_{t} | \mathbf{x}_{>t}\right)。由于 AR 语言模型仅被训练用于编码单向(uni-directional)语境(前向或后向),因而在深度双向语境建模中效果不佳。而下游语言理解任务通常需要双向语境信息,导致 AR 语言建模无法实现有效预训练。

  • 自编码(autoencoding, AE):相关的预训练模型不会进行明确的密度估计(explicit density estimation),而是从残缺的(corrupted)输入中重建原始数据。例如bert,使用一定比例的[MASK],然后预测被mask掉的是什么东西。由于目标并不是密度估计,所以在重建的时候,可以考虑双向的上下文信息。但存在如下两个问题:

    • finetune时的真实数据缺少预训练期间使用的[MASK]这些mask信息,这导致预训练和微调效果的差异(pretrain-finetune discrepancy)

    • 输入中要预测的token是被mask掉的,所以无法像AR那样使用乘积rule来建立联合概率分布。也就是说,给定未mask的 token,BERT假设预测的token之间彼此独立,这其实是对自然语言中普遍存在的高阶、长期依赖关系的一种过度简化

基于这些优缺点,提出了一种泛化的自回归预训练模型XLNet:

  • Permutation Language Model(PLM):在自回归LM模式下,最大化所有可能的因式分解顺序的对数似然,学习双向语境信息;

    • 把原来的[MASK]这个token干掉了,转而用attention mask来搞

    • 引入了排列:即原来是1234,可以输入3241,这个时候改一下mask就行

  • 引入了Transformer-XL的主要思路:相对位置编码以及分段RNN机制,实践已经证明这两点对于长文档任务是很有帮助的

更多的任务

MT-DNN

Multi-Task Deep Neural Networks for Natural Language Understanding

其他改进

RoBERTa

参考重回榜首的BERT改进版开源了,千块V100、160GB纯文本的大模型

RoBERTa: A Robustly Optimized BERT Pretraining Approach

  • 修改了一些超参

  • 删掉nsp任务

  • 更大的batchsize和学习率

RoBERTa中文预训练模型,你离中文任务的「SOTA」只差个它

https://github.com/brightmart/roberta_zh

DeBERTa

Deberta: Decoding-enhanced bert with disentangled attention

  • disentangled attention mechanism:每个词使用2个向量,HH编码内容,PP编码相对位置,i和j间的attention如下:

Ai,j={Hi,Pij}×{Hj,Pji}T=HiHjT+HiPjiT+PijHjT+PijPjiT\begin{array}{r} \mathrm{A}_{\mathrm{i}, \mathrm{j}}=\left\{\mathrm{H}_{\mathrm{i}}, \mathrm{P}_{\mathrm{i} \mid \mathrm{j}}\right\} \times\left\{\mathrm{H}_{\mathrm{j}}, \mathrm{P}_{\mathrm{j} \mid \mathrm{i}}\right\}^{\mathrm{T}} \\ =\mathrm{H}_{\mathrm{i}} \mathrm{H}_{\mathrm{j}}^{\mathrm{T}}+\mathrm{H}_{\mathrm{i}} \mathrm{P}_{\mathrm{j} \mid \mathrm{i}}^{\mathrm{T}}+\mathrm{P}_{\mathrm{i} \mid \mathrm{j}} \mathrm{H}_{\mathrm{j}}^{\mathrm{T}}+\mathrm{P}_{\mathrm{i} \mid \mathrm{j}} \mathrm{P}_{\mathrm{j} \mid \mathrm{i}}^{\mathrm{T}} \end{array}
  • 预训练:使用enhanced mask decoder,把绝对位置信息引入解码层,来预估被mask掉的token

  • finetune:virtual adversarial training

ELECTRA

2019最佳预训练模型:非暴力美学,1/4算力超越RoBERTa

ELECTRA: 超越BERT, 19年最佳NLP预训练模型

ELECTRA: pre-training text encoders as discriminators rather than generators

使用新的预训练task:RTD(replaced token detection):

  • 使用一个生成网络采样出token,替换一些原有的token

  • 训练一个判别模型,预估一个token是否是被替换的

RTD比MLM更sample-efficient,因为RTD只做二分类,而MLM需要做全词表的softmax

sentence Transformers

俄罗斯套娃 (Matryoshka) 嵌入模型概述

Matryoshka representation learning

俄罗斯套娃嵌入模型旨在将更重要的信息存储在早期的维度中,将不太重要的信息存储在后面的维度中。俄罗斯套娃嵌入模型的这一特点允许我们截断模型产生的原始 (大) 嵌入,同时仍保留足够的信息以在下游任务上表现良好。

https://sbert.net/examples/training/matryoshka/README.html

训练时:

from sentence_transformers import SentenceTransformer
from sentence_transformers.losses import CoSENTLoss, MatryoshkaLoss

model = SentenceTransformer("microsoft/mpnet-base")

base_loss = CoSENTLoss(model=model)
loss = MatryoshkaLoss(
    model=model,
    loss=base_loss,
    matryoshka_dims=[768, 512, 256, 128, 64],
    matryoshka_weight=[1, 1, 1, 1, 1],
)

model.fit(
    train_objectives=[(train_dataset, loss)],
    ...,
)

预测时

from sentence_transformers import SentenceTransformer
from sentence_transformers.util import cos_sim

model = SentenceTransformer("tomaarsen/mpnet-base-nli-matryoshka")

matryoshka_dim = 64
embeddings = model.encode(
    [
        "The weather is so nice!",
        "It's so sunny outside!",
        "He drove to the stadium.",
    ]
)
embeddings = embeddings[..., :matryoshka_dim] # Shrink the embedding dimensions
print(embeddings.shape)
# => (3, 64)

# Similarity of the first sentence to the other two:
similarities = cos_sim(embeddings[0], embeddings[1:])
print(similarities)
# => tensor([[0.8910, 0.1337]])

注意,如果嵌入已经归一化,那么在截断后它们将不再归一化,因此你可能需要重新归一化

from sentence_transformers import SentenceTransformer
from sentence_transformers.util import cos_sim
import torch.nn.functional as F

model = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)

matryoshka_dim = 64
embeddings = model.encode(
    [
        "search_query: What is TSNE?",
        "search_document: t-distributed stochastic xxx.",
        "search_document: Amelia Mary Earhart was xxx.",
    ],
    convert_to_tensor=True,
)
# The Nomic team uses a custom architecture, 
# making them recommend Layer Normalization before truncation
embeddings = F.layer_norm(embeddings, normalized_shape=(embeddings.shape[1],))
embeddings[..., :matryoshka_dim] # Shrink the embedding dimensions

similarities = cos_sim(embeddings[0], embeddings[1:])
# => tensor([[0.7154, 0.4468]])

更小的BERT

BERT 瘦身之路:Distillation,Quantization,Pruning

albert

刚刚,Google发布24个小型BERT模型,直接通过MLM损失进行预训练

ALBERT:用于语言表征自监督学习的轻量级 BERT

谷歌ALBERT模型V2+中文版来了:之前刷新NLP各大基准,现在GitHub热榜第二

超小型BERT中文版横空出世!模型只有16M,训练速度提升10倍

https://github.com/brightmart/albert_zh

预训练小模型也能拿下13项NLP任务,谷歌ALBERT三大改造登顶GLUE基准

ALBERT 模型在 GLUE、RACE 和 SQuAD 基准测试上都取得了新的 SOTA 效果,并且参数量还少于 BERT-large。

ALBERT: a lite bert for self-supervised learning of language representations

通过对词嵌入矩阵进行因式分解,再为下游任务共享不同层的所有参数,这样可以大大降低 BERT 的参数量。

还提出了一种新型句间连贯性损失函数,它可以强迫模型学习句间的连贯性表达,从而有利于各种下游 NLP 任务。

ALBERT 通过两个参数削减技术克服了扩展预训练模型面临的主要障碍。

  • 对嵌入参数化进行因式分解:将大的嵌入矩阵分解为两个小的矩阵,从而将隐藏层的大小与词汇嵌入的大小分离开来。这种分离使得隐藏层的增加更加容易,同时不显著增加词汇嵌入的参数量。

  • 跨层参数共享:避免参数量随着网络深度的增加而增加。

两种技术都显著降低了 BERT 的参数量,同时不对其性能造成明显影响,从而提升了参数效率。ALBERT 的配置类似于 BERT-large,但参数量仅为后者的 1/18,训练速度却是后者的 1.7 倍。这些参数削减技术还可以充当某种形式的正则化,可以使训练更加稳定,而且有利于泛化。

为了进一步提升 ALBERT 的性能,还引入了一个自监督损失函数,用于句子级别的预测(SOP)。SOP 主要聚焦于句间连贯,用于解决原版 BERT 中下一句预测(NSP)损失低效的问题。

albert_tiny:

input_ids先查word_embeddings(\(V\times E=21118*128)),得到dim=128的表示,再查word_embeddings_2(\(E\times M =128*312\)),得到dim=312的表示。

搞positionembedding时,并不用输入0 1 2...,只需要做一些slice的变换就行了

    with tf.control_dependencies([assert_op]):
      full_position_embeddings = tf.get_variable(
          name=position_embedding_name,
          shape=[max_position_embeddings, width],
          initializer=create_initializer(initializer_range))
      # Since the position embedding table is a learned variable, we create it
      # using a (long) sequence length `max_position_embeddings`. The actual
      # sequence length might be shorter than this, for faster training of
      # tasks that do not have long sequences.
      #    
      # So `full_position_embeddings` is effectively an embedding table
      # for position [0, 1, 2, ..., max_position_embeddings-1], and the current
      # sequence has positions [0, 1, 2, ... seq_length-1], so we can just
      # perform a slice.
      position_embeddings = tf.slice(full_position_embeddings, [0, 0],
                                     [seq_length, -1]) 
      num_dims = len(output.shape.as_list())

      # Only the last two dimensions are relevant (`seq_length` and `width`), so
      # we broadcast among the first dimensions, which is typically just
      # the batch size.
      position_broadcast_shape = [] 
      for _ in range(num_dims - 2):
        position_broadcast_shape.append(1)
      position_broadcast_shape.extend([seq_length, width])
      position_embeddings = tf.reshape(position_embeddings,
                                       position_broadcast_shape)
      output += position_embeddings

然后会通过create_attention_mask_from_input_mask把input_ids和input_mask搞一下,得到attention_mask去和attention做mask,主要是算loss啥的,把后面的mask掉不算

distillbert

参考小版BERT也能出奇迹:最火的预训练语言库探索小巧之路

1.4w个stars。。

https://huggingface.co/transformers

DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter

tinybert

TinyBERT:模型小7倍,速度快8倍,华中科大、华为出品

TinyBERT: Distilling BERT for Natural Language Understanding

提出了一个two-stage learning framework,在pre-training阶段和task-specific阶段都进行distill。

相比baseline,只有28% parameters和31%的inference时间

在glue上,7.5x小,infer上有9.4x快。

哪吒”出世!华为开源中文版BERT模型

NEZHA: Neural Contextualized Representation for Chinese Language Understanding

https://github.com/huawei-noah/Pretrained-Language-Model

华为诺亚方舟开源哪吒、TinyBERT模型,可直接下载使用

reformer

哈希革新Transformer:这篇ICLR高分论文让一块GPU处理64K长度序列

Reformer: The Efficient Transformer

https://github.com/google/trax/blob/master/trax/models/research/reformer.py

大型的 Transformer 往往可以在许多任务上实现 sota,但训练这些模型的成本很高,尤其是在序列较长的时候。在 ICLR 的入选论文中,我们发现了一篇由谷歌和伯克利研究者发表的优质论文。文章介绍了两种提高 Transformer 效率的技术,最终的 Reformer 模型和 Transformer 模型在性能上表现相似,并且在长序列中拥有更高的存储效率和更快的速度。论文最终获得了「8,8,6」的高分。在最开始,文章提出了将点乘注意力(dot-product attention)替换为一个使用局部敏感哈希(locality-sensitive hashing)的点乘注意力,将复杂度从 O(L2 ) 变为 O(L log L),此处 L 指序列的长度。此外,研究者使用可逆残差(reversible residual layers)代替标准残差(standard residuals),这使得存储在训练过程中仅激活一次,而不是 n 次(此处 n 指层数)。最终的 Reformer 模型和 Transformer 模型在性能上表现相同,同时在长序列中拥有更高的存储效率和更快的速度。

大幅减少GPU显存占用:可逆残差网络(The Reversible Residual Network)

LTD-bert

内存用量1/20,速度加快80倍,腾讯QQ提出全新BERT蒸馏框架,未来将开源

Q-bert

AAAI 2020 | 超低精度量化BERT,UC伯克利提出用二阶信息压缩神经网络

Q-BERT: Hessian Based Ultra Low Precision Quantization of BERT

Adabert

推理速度提升29倍,参数少1/10,阿里提出AdaBERT压缩方法

AdaBERT: Task-Adaptive BERT Compression with Differentiable Neural Architecture Search

仅解码器的GPT

AI也能精彩表达:几种经典文本生成模型一览

GPT

2018年的Improving language understanding by generative pre-training,生成式预训练(Generative pre-training, gpt),用transformer的decoder,参数量117m(0.1b),无监督预训练和有监督微调。

微调阶段为每种下游任务专门设计:

  • 分类:输入一段文本,经过transformer,最后接一个Linear

  • entailment(蕴含):输入2段文本,premise(假设)和hypothesis(假说),经过transformer,最后接一个Linear

  • 相似度:输入2段文本a和b,a+b过transformer,b+a过transformer,再合起来接一个Linear

  • 多选题:输入context+答案1过transformer+Linear,答案2、答案3同样操作,将3个输出合在一起求softmax

GPT2

2019年的Language models are unsupervised multitask learners模型结构小改,增加数据,参数量变大为15亿(1.5b),无监督语言建模。

  • layernorm前移到每个sub-block之前

  • additional layernorm在最后的self-attention block才加上

  • 修改初始化方法,以考虑残差路径上的累积并缩放残差层的权重

15亿参数最强通用NLP模型面世!Open AI GPT-2可信度高于所有小模型

中文GPT2

只需单击三次,让中文GPT-2为你生成定制故事

https://github.com/imcaspar/gpt2-ml

https://colab.research.google.com/github/imcaspar/gpt2-ml/blob/master/pretrained_model_demo.ipynb

语言模型秒变API,一文了解如何部署DistilGPT-2

huggingface的distill gpt-2:https://github.com/huggingface/transformers

编码器+解码器

T5

谷歌T5模型刷新GLUE榜单,110亿参数量,17项NLP任务新SOTA

谷歌最新T5模型17项NLP任务霸榜SuperGLUE,110亿参数量!

Exploring the limits of transfer learning with a unified text-to-text transformer

将所有NLP任务都建模成text-to-text的生成任务,

mT5(mt5: A massively multilingual pre-trained text-to-text transformer)是T5的变种,基于新的Common Crawl的数据集(包括101种语言)上预训练

MASS

MASS: Masked Sequence to Sequence Pre-training for Language Generation

bert只使用了Transformer的encoder部分,其下游任务也主要是适用于自然语言理解(NLU),对于类似文本摘要、机器翻译、对话应答生成等自然语言生成(NLG)任务显然是不太合适的。MASS 采用了编码器-解码器框架,并尝试在给定部分句子的情况下修复整个句子。MASS输入句子包含了一些连续的 Token,并且中间会带有一些连续的Mask,模型的任务是预测出被Mask掉的词是什么。相比 BERT 只有编码器,MASS 联合训练编码器与解码器,能获得更适合机器翻译的表征能力。

训练步骤主要分为两步:

  • Encoder: 输入为被随机mask掉连续部分token的句子,使用Transformer对其进行编码;这样处理的目的是可以使得encoder可以更好地捕获没有被mask掉词语信息用于后续decoder的预测;

  • Decoder: 输入为与encoder同样的句子,但是mask掉的正好和encoder相反,和翻译一样,使用attention机制去训练,但只预测encoder端被mask掉的词。该操作可以迫使decoder预测的时候更依赖于source端的输入而不是前面预测出的token,防止误差传递。

BART

多项NLP任务新SOTA,Facebook提出预训练模型BART​

BART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension

BART是一个适用于seq2seq模型的去噪自编码器。预训练包括两个阶段:

  • 使用任意噪声函数破坏文本

  • 用seq2seq模型来重建原始文本

LLM概述

PLM(pretrained language models),即bert等

LLM简史

LLM列表(持续更新中)

  • 百亿:除了LLaMA(最大650亿)和NLLB(最大545亿),大多数在100亿-200亿之间,通常需要数百甚至上千个GPU或TPU。

  • 千亿:OPT、OPT-IML、BLOOM和BLOOMZ与GPT-3(175B)大致相同,GLM有1300亿,Galactica有1200亿,通常需要数千个GPU或者TPU。

ckpt?模型发布时间大小预训练数据规模硬件训练时间

Y

2019.10

11B

1万亿tokens

1024 TPU v3

-

N

2020.05

175B

3000万亿tokens

-

-

N

2020.06

600B

1万亿tokens

2048 TPU v3

4天

Y

2020.10

13B

1万亿tokens

-

-

Y

2021.04

13B

1.1TB

2048 Ascend 910

-

Y

2021.06

198B

2.6TB

-

-

N

2021.07

12B

1000万亿tokens

-

-

N

2021.07

10B

3750亿tokens

384 v100

-

N

2021.08

178B

3000亿tokens

800 GPU

-

N

2021.09

82B

3000亿tokens

1024 A100

13.4天

N

2021.09

137B

-

128 TPU v3

60小时

N

2021.10

245B

1800亿tokens

2128 GPU

-

Y

2021.10

11B

-

512 TPU v3

27小时

N

2021.12

52B

4000亿tokens

-

-

N

2021.12

175B

-

-

-

N

2021.12

280B

3000亿tokens

4096 TPU v3

920小时

N

2021.12

260B

-

-

-

N

2021.12

1200B

2800亿tokens

1024 TPU v4

574小时

N

2022.01

137B

7680亿tokens

1024 TPU v3

57.5天

N

2022.01

530B

2700亿tokens

4480 80G A100

-

N

2022.02

41B

9670亿tokens

-

-

N

2022.03

175B

-

-

-

N

2022.03

70B

1.4万亿tokens

-

-

Y

2022.03

16B

5770亿tokens

-

-

Y

2022.04

20B

825GB

96 40G A100

-

Y

2022.04

11B

-

256 TPU v3

4小时

N

2022.04

540B

7800亿tokens

6144 TPU v4

-

Y

2022.05

20B

825GB

96 40G A100

-

Y

2022.05

175B

1800亿tokens

992 80G A100

-

Y

2022.07

54.5B

-

-

-

N

2022.08

20B

1.3万亿tokens

128 A100

120天

N

2022.09

70B

64 TPU v3

-

-

N

2022.09

10B

3000亿tokens

128 A100 40G

24天

N

2022.10

540B

-

512 TPU v4

5天

N

2022.10

540B

-

512 TPU v4

37小时

N

2022.10

540B

-

-

-

Y

2022.10

130B

4000亿tokens

768 40G A100

60天

Y

2022.10

11B

-

-

-

Y

2022.11

176B

3660亿tokens

384 80G A100

105天

Y

2022.11

13B

-

-

-

Y

2022.11

120B

1060亿tokens

-

-

Y

2022.11

176B

-

-

-

Y

2022.12

175B

-

128 40G A100

-

Y

2023.02

65B

1.4万亿tokens

2048 80G A100

21天

N

2023.03

-

-

-

-

Y

2022.09

13B

8500亿tokens

1536 Ascend 910

60天

N

2023.03

1085B

3290亿tokens

512 Ascend 910

100天

Y

2023.04

12B

3000亿tokens

256 40G A100

-

可以直接把对应的md丢给gpt,叫它导出一个excel,然后就可以自定义排序或者画散点图看了

LLM数据集

llm中文数据集:https://juejin.cn/post/7238921093553438779

  • Books:

    • BookCorpus:超过11000本电子书,用于GPT和GPT-2。

    • Gutenberg:超过70000本文学作品,包括小说、散文、诗歌、戏剧、历史、科学、哲学和其他公共领域,用于MT-NLG和LLaMA。

    • Books1和Books2:比BookCorpus大得多,但未公开,用于GPT-3。

  • CommonCrawl:最大的开源网络爬虫数据库之一,百万亿字节,有大量噪音和低质信息,需要过滤,有如下4个子集:

    • C4:包括en(806G,训练T5、LaMDA、Gopher、UL2)、en.noclean(6T)、realnewslike(36G)、webtextlike(17G)、multilingual(38T,训练mT5)。

    • CC-Stories:31G,内容以故事的形式展示

    • CC-News:76G

    • RealNews:120G

  • Reddit Links:Reddit上的帖子,高赞通常比较有用,可以拿来创建高质量数据集。

    • WebText:由Reddit上的高赞链接组成,未公开,对应的开源版是OpenWebText

    • Pushshift.io:实时更新的数据集,包括Reddit自创建以来的历史数据,有数据存储,也有实用工具,供用户搜索、总结和统计分析。

  • Wikipedia:大部分文章使用写作风格,并支持引用,英语版本用于大多数LLM,如GPT-3、LaMDA、LLaMA,还有多语言版。

  • Code:包括开源许可证的公共代码库(如github)和与代码相关的问答平台(如StackOverflow),Google公开了BigQuery数据集,CodeGen用的BIGQUERY是其的一个子集。

  • 其他:

    • The Pile有800G,包括书籍、网站、代码、科学论文和社交媒体平台,有22个子集,用于GPT-J(6B)、CodeGen(16B)、Megatron-Turing NLG(530B)。

    • ROOTS由各种小数据集组成,共1.6T,包括59种语言(自然语言和编程语言),用于BLOOM。

LLM开源库

一些综述

扩展法则(scaling law)

openai的扩展法则

2020年,openai的Scaling laws for neural language models通过拟合模型在不同数据大小(2000w到230亿个token)、不同的模型大小(7.68亿到15亿个非嵌入参数)的性能,提出了在计算预算cc的条件下,LL是用nats表示的交叉熵损失,模型性能与模型规模NN数据集规模DD以及训练计算量CC间存在如下幂律关系:

L(N)=(NcN)αN,αN0.076,Nc8.8×1013L(N)=(\frac{N_c}{N})^{\alpha _N}, {\alpha}_N\sim 0.076,N_c\sim 8.8\times 10^{13}

L(D)=(DcD)αD,αD0.05,Nc5.4×1013L(D)=(\frac{D_c}{D})^{\alpha _D}, {\alpha}_D\sim 0.05,N_c\sim 5.4\times 10^{13}

L(C)=(CcC)αC,αC0.05,Cc3.1×108L(C)=(\frac{C_c}{C})^{\alpha _C}, {\alpha}_C\sim 0.05,C_c\sim 3.1\times 10^{8}

其中,NcN_c表示非嵌入参数数量,DcD_c表示训练token数量,CcC_c表示FP-days。

Go Wider Instead of Deeper说了,transformer效果的提升不在于计算量的变大,而应该在于通过提升模型的hidden dim来增加模型参数量

Chinchilla扩展法则

DeepMind在Training compute-optimal large language models中提出了Chichilla扩展法则来指导LLM最优计算量的训练。通过变化更大范围的模型大小(7000w到160亿参数)和数据大小(50亿到5000亿个token)进行实验,拟合了如下的扩展法则:

L(N,D)=E+ANα+BDβL(N, D)=E+\frac{A}{N^\alpha}+\frac{B}{D^\beta}

其中E=1.69,A=406.4,B=410.7,α=0.34,β=0.28E=1.69,A=406.4,B=410.7,\alpha = 0.34, \beta =0.28,通过在约束条件C6NDC\approx 6ND下优化损失L(N,D)L(N,D),将计算预算最优地分配给模型大小和数据大小的方法:

Nopt(C)=G(C6)a,Dopt(C)=G1(C6)bN_{o p t}(C)=G\left(\frac{C}{6}\right)^a, \quad D_{o p t}(C)=G^{-1}\left(\frac{C}{6}\right)^b

其中a=αα+βa=\frac{\alpha}{\alpha+\beta}b=βα+βb=\frac{\beta}{\alpha+\beta}GG是由A,B,α,βA,B,\alpha,\beta计算出的扩展系数。

随着计算预算的增加,

  • openai的扩展法则更偏向于将更大预算分给模型大小,因为其对比各模型时使用了固定的训练数据量和学习率等超参,低估了数据量的作用。每增加10倍的计算量,应该让数据集大小增加为约1.8倍,模型参数量增加为约5.5倍。即模型参数量更加的重要

  • Chinchilla扩展法则认为模型大小和数据大小要同比例增加,即aabb取值差不多。因为其在无视模型大小的前提下,发现设置与数据量差不多match的学习率能获得更好的loss。每增加10倍的计算量,应该让数据集大小增加为约3.16倍,模型参数量也增加为约3.16倍。即数据集大小和模型参数量一样重要

然而,有一些能力(如涌现)无法根据扩展法则进行预测,只有当模型达到一定规模时才会出现。

飘红的就是常见的10B模型,大概要205B的token来训练,能达到计算最优点,当然并不一定是loss最小的点,这个可以参考llama3的现象

scaling law的一些讨论

Scaling Laws 又失灵了?谷歌新研究:扩散模型不是越大越好

Bigger is not Always Better: Scaling Properties of Latent Diffusion Models

涌现能力

涌现能力:在小型模型中不存在而在大型模型中产生的能力,当规模达到一定程度时,性能显著提升,超出随机水平(参考 Emergent Abilities of Large Language Models)。与物理学中的相变现象类似(物质从一种相(状态)转变为另一种相的过程,通常伴随着能量的吸收或释放,并且涉及不同的物理性质,例如固体、液体和气体之间的转变)。

普林斯顿DeepMind用数学证明:LLM不是随机鹦鹉!「规模越大能力越强」有理论根据

A Theory for Emergence of Complex Skills in Language Models

LLM的3种典型涌现能力及其对应代表模型:

上下文学习(in-context learning)

GPT-3(Language models are few-shot learners)提出,只要提供一个自然语言指令和/或几个任务演示,语言模型就能通过完成输入文本的词序列的方式来为测试实例生成预期输出,不用额外的梯度更新。

  • ICL能力小模型不具备:1750亿的GPT-3有ICL能力,但GPT-1和GPT-2无此能力。

  • ICL能力取决于具体下游任务:130亿的GPT-3能在算术任务上有ICL,但1750亿的GPT-3在波斯语QA上无能为力。

指令遵循(instruction following)

使用自然语言描述的混合多任务数据集进行微调(指令微调),LLM在未见过的以指令形式描述的任务上表现出色,具有更好的泛化能力。例如Multitask prompted training enables zero-shot task generalizationTraining language models to follow instructions with human feedbackFinetuned language models are zero-shot learners

Finetuned language models are zero-shot learners的实验中,当模型大小达到680亿时,经过指定微调的LaMDA-PT开始在未见过的任务上显著优于未微调的模型,而80亿或更小的模型则没有这个现象。

Scaling instruction-finetuned language models的实验中,PaLM至少在620亿参数上才能在4个评估基准的各种任务上表现良好。

逐步推理(multi-step reasoning)

对于涉及多个推理步骤的复杂任务(如数学),可以使用思维链(Chain-of-Thought, CoT)提示策略(Chain of thought prompting elicits reasoning in large language models),让LLM通过利用中间推理步骤的提示机制来解决这类任务。

Chain of thought prompting elicits reasoning in large language models发现,CoT在模型大于600亿的PaLM和LaMBDA变体中能够提升在算术推理基准任务的效果,而当模型大于1000亿时,相比标准提示的优势更明显。

How does GPT Obtain its Ability? Tracing Emergent Abilities of Language Models to their Sources

LLM关键点

如何让LLM能够通用有能力

扩展

更大的模型、数据规模和更多的训练计算,但计算预算是有限的,可以用扩展法更高效地分配计算资源,如Chinchilla在相同计算预算下增加训练token数,优于更大模型规模的Gopher,同时需要数据清理。

训练

能力引导

当LLM执行某些特定任务时,可能不会显式地展示出其通用求解器的能力,设计合适的任务指令或具体的ICL策略可以激发这种能力,例如

  • 通过包含中间推理步骤的CoT提示

  • 使用自然语言表达的任务描述,对LLM进行指令微调

对齐微调

由于预训练语料库包括高质量和低质量的数据,LLM可能生成有毒、偏见甚至有害的内容,要让LLM和人类价值观保持一致,如有用性、诚实性和无害性。RLHF相关工作如Training language models to follow instructions with human feedbackDeep reinforcement learning from human preferences能够产生高质量、无害的回答(例如拒绝回答侮辱性问题)。

工具操作

LLM本质是基于海量文本语料库进行文本生成训练的,对于不适合以文本形式表达的任务表现不佳(如数字计算),且其能力受限于预训练数据,无法获取最新信息。可以利用外部工具:

数据收集

数据获取

  • 通用文本数据:

    • 网页:例如CommonCrawl,同时需要过滤和处理以提高质量

    • 对话文本:公共对话数据如PushShift.io,对于在线社交媒体的对话数据,可以转换成树形结构,每句话与回应其的话相连。多方的对话树可以划分为预训练语料库中的多个子对话。过度引入对话数据可能会有潜在风险(OPT: open pre-trained transformer language models):陈述性指令和直接疑问句被错误地认为是对话的开始,导致指令的有效性下降。

    • 书籍:更正式的长文本,利于学习语言知识建模长期依赖关系生成叙述性和连贯的文本

  • 专用文本数据:

    • 多语言文本:BLOOM的预训练语料中包括了46种语言,PaLM包含了122种

    • 科学文本:如arxiv论文、科学教材、数学 网页等,通常需要特定的标记化和预处理。

    • 代码:一是编程问答社区,二是开源代码仅为。对应长距离依赖和准确的执行逻辑,可能是复杂推理能力的来源。将推理任务格式化为代码形式还能帮LLM生成更准确的结果(如Language models of code are few-shot commonsense learnersAutoformalization with large language models

数据预处理

  • 质量过滤:有一些基于分类器的方法,例如维基百科的数据为正样本,负采样其他数据训练二分类器,但这种方法会删除方言、口语和社会语言的高质量文本,可能导致有偏、减少多样性。还有启发式的方法,主要包括:

    • 基于语言的过滤:如果该llm主要用于某种语言,可以把其他语言删了

    • 基于度量的过滤:利用生成文本的评估度量(如perplexity)来检测和删除不自然的句子

    • 基于统计的过滤:如标点符号分布符号和单词比例句子长度

    • 基于关键词的过滤:删除噪声或无用元素,如HTML标签超链接模板攻击性词语等。

  • 去重:Scaling laws and interpretability of learning from repeated data中发现重复数据会降低多样性,可能导致训练不稳定。下面3个级的去重都很有用

    • 句子级:删掉包含重复单词和短语的句子,因为可能在语言建模中引入重复模式The curious case of neural text degeneration)(后面的章节会讲)

    • 文档级:通过文档间的表层特征(如n-gram或单词重合率)来删掉重复文档

    • 数据集级:训练集中删掉测试集可能出现的重复文本,防止训练集和评估集间的重叠

  • 隐私去除:删掉可识别个人信息(PII),如基于关键词(姓名、地址、电话号码)识别。另外,Deduplicating Training Data Mitigates Privacy Risks in Language Models发现LLM在隐私攻击下的脆弱性可能归因于预训练语料中存在重复PII数据

  • 分词:可以直接利用已有分词器,也可以使用专门为预训练语料库设计的分词器,如SentencePiece,而且BPE(byte pair encoding)能确保分词后的信息不会丢失,但其中的如NFKC(Unicode normalization forms)的归一化技术可能会降低分词的性能

预训练语料的重要性

  • 混合来源:不同领域和场景的数据能让LLM有更强大的泛化能力。需要仔细设置数据分布,Gopher对数据分布消融,发现增加书籍数据可以提升捕捉长期依赖的能力,增加c4数据集比例可以提升其在c4验证集上的效果,但单独训练过多的某个领域数据会影响LLM在其他领域的泛化能力。

  • 数据量:模型性能方面,数据大小也能看到与模型大小类似的扩展法则。LLaMA发现,用更多数据训练更长时间,较小的模型也能实现良好性能。

  • 数据质量:Gopher、GLaM和T5都发现,在清理后的数据上训练能提升llm效果。数据的重复可能导致『双下降现象』(Scaling laws and interpretability of learning from repeated dataDeep double descent: Where bigger models and more data hurt),甚至会导致训练不稳定。此外,Scaling laws and interpretability of learning from repeated data还发现,重复数据会降低LLM从上下文复制的能力,从而影响ICL中的泛化能力

注:双下降指的是随着模型复杂性的增加,可能loss先下降,然后再升高,最后又下降

  • 当模型的复杂性低于数据的复杂性时,增加模型的复杂性可以帮助减少训练误差。

  • 当模型的复杂性超过数据的复杂性时,增加模型的复杂性反而可能导致训练误差增加。这是因为模型开始过拟合数据,捕获数据中的噪声而非实际的模式。

  • 当模型的复杂性远大于数据的复杂性时,训练误差可能再次开始减少。这是因为模型有足够的能力来对数据的噪声进行平滑,同时仍然能够捕获数据的实际模式。

LLM模型架构

MoE原理

MoE模型的前世今生

MoE 系列论文解读:Gshard、FastMoE、Tutel、MegaBlocks 等

CMU开源GRIFFIN:一种新颖的无需训练的MoE方法,提高大模型的生成效率!

Prompt-prompted Mixture of Experts for Efficient LLM Generation

主流框架

  • 编码器-解码器架构(encoder-decoder):标准Transformer,如T5、BART,只有少数LLLM还用这种结构,如Flan-T5

  • 因果解码器架构(causual decoder):也叫decoder-only单向注意力掩码,输入和输出token通过解码器以相同方式进行处理,以GPT系列为代表,现有大部分LLM都是这种架构,如OPT、BLOOM、Gopher等。

  • 前缀解码器架构(prefix decoder):修正因果解码器的掩码机制,使其能对前缀token执行双向注意力,并且仅对生成的token执行单向注意力(和encoder-decoder类似),即Unified language model pre-training for natural language understanding and generation提出的uni-lm。What language model architecture and pretraining objective works best for zero-shot generalization?建议不从头开始预训练,而是继续训练因果编码器,然后将其转换成前缀编码器以加速收敛。例如U-PaLM从PaLM演化而来,还有GLM-130B也是这种架构。

https://github.com/microsoft/unilm

对于这3种架构,都可以用MoE进行扩展,每个输入的一小部分神经网络权重稀疏激活,如Switch Transformer和GLaM。Unified scaling laws for routed language models发现,通过增加专家数量或总参数大小,性能会有显著改进。

讨论:为什么现在的LLM都是Decoder only的架构?

https://www.zhihu.com/question/588325646/answer/2940298964