前语

之前的内容中,将入门+跟进chatGPT-SOTA并构成自己的认知系统的内容做了整理:zhuanlan.zhihu.com/p/627641026

。但由于篇幅所限,内容又涵盖了技能和非技能的部分,关于许多技能的介绍不够。本文就与咱们一同整理练习一个ChatGPT的流程和示例代码,做中学。依据此,经过对各个模块的优化改善,就能够得到自己的模型。虽然当时开源完结现已苍茫多,但假如想要做出创新或许优化,乃至了解模型的一些体现,也需求对根底的底层完结有所了解。

本文总共分为五个末节,榜首节核心是以GPT为例整理完结一个基座模型LLM;第二节则是介绍增量学习与微调,一起介绍了SFT和alignment(对齐)两个在chatGPT中发挥重要作用的概念,到此为止就能够拿到一个体现不错的chatllm了;第三节介绍奖赏模型和PPO,这部分让人类的参加开释了出来,使得模型自优化“对齐”变得主动化;第四节针对一些微调的要害点进行剖析,并对练习运用的数据相关要点做了一个整合。第五节是一个小结。

PS:多卡的试验我没有做,由于穷(求包养 谢谢~

榜首节:完结一个LLM

完结一个LLM,首要需求完结一个LM,依据之前对言语模型的介绍zhuanlan.zhihu.com/p/608047052…** Language Models,当时咱们以 GPT 为代表,实质学习的是给定前序行列,然后预估下一个单词是什么的问题。为了深化了解GPT是怎么进化到ChatGPT的,咱们会从GPT1开端逐步晋级直到GPT3。

依据之前剖析过复现需求重视的维度,咱们从数据和模型两个维度切入来描绘复现进程

PS:GPT刚出来的论文名叫Generative Pre-Training,这是GPT的全名,当时的盛况让我想到了熊猫和猫熊。

模型 介绍
GPT4 预计参数量1-1.7w亿,支持文本和图画,输出文本(可是能够支持编程绘图),在各项使命上体现更好
GPT3.5(instructGPT和chatGPT) 1750亿参数,文字输入输出;规范了Alignment这个概念,规范了练习流程:SFT、RLHF(RW+PPO);依据上文,咱们看到这儿集合了WebGPT和CodeX的长处。
GPT3 1750亿参数,文字输入输出。提出in-context learning(0/few-shot)
GPT2 15亿参数,文字输入输出。弱化版GPT3,也是咱们摸索GPT3的重要参阅
GPT1 1.17亿参数,文字输入输出,无监督预练习,task oriented finetuning->下流使命上需求finetune,没有满足泛化性,一起finetune需求数据

这个项目中,咱们依据nanoGPT进行模型完结

GPT的模型结构规划

这个末节独自对模型细节做一次介绍,代码完结在后续独自讲解。

GPT1清晰-学习方针

论文中清晰了学习方针,便是学习练习一个言语模型。给定一个数据集U=u1,,,unU={u_1,,,u_n},最大化以下log-likelihood:

L1(U)=∑ilogP(ui∣ui−k,…,ui−1;)L_1(U)=\sum_{i}logP(u_i|u_{i-k},…,u_{i-1};\theta)

k标明上下文窗口设定, \theta标明神经网络的参数,L(U)标明联合概率,留意对数函数相加,底数和对数的改变。

咱们都知道GPT运用了Transformer的解码器部分来作为言语模型,该模型在输入上运用多头自留意操作,然后经过方位感知的前馈层生成方针token的输出散布。在整个模型架构中,能够标明为以下三个公式,一起咱们在模型架构图中标明出了对应的额输入和输出:

h0=UWe+Wph_0=UW_e+W_p :输入层,将方位向量和单词向量输入

hl=transformer_block(ht−1)∀i∈[1,n]h_l=transformer\_block(h_{t-1})\foralli\in[1,n]:将输入层的输出输入到transformer中,经过多层重复核算

p(u)=softmax(hnWeT)p(u)=softmax(h_nW_e^T):将最后一个transformer的输出输入到softmax层中,针对不同的单词输出终究的概率散布

其间u是上下文的token的向量,n是层数,W_e是word embeeding matrix,W_p是position embedding matrix。

暂时无法在飞书文档外展现此内容

GPT1奠定根底-模型结构规划

要点归结如下,GPT-2会依据此进行必定修正:

  • 首要依据原始的Transformer作业运用了一个12层的deocoder-only Transformer模型,其间包含masked self-attention heads(768d和12个heads)
  • 关于position-wise feed forward networks,运用了3072d inner states
  • 运用了Adam优化器,最大学习速率为2.5e-4。学习率在前2000次更新期间线性添加,并依据cosine-schedule降至0
  • 模型采用残差、嵌入和留意力的dropout,正则化率为0.1。一起还采用了L2正则化的修正版别,其间一切非偏置或增益权重的w=0.01
  • 激活函数运用了GELU
  • 运用了依据学习的方位嵌入,而非transformer中的正弦版别
  • 咱们运用ftfy库整理BooksCorpus中的原始文本,对一些标点和空格进行规范化,并运用spaCy分词器。
  • 由于Layernorm在整个模型中被广泛运用,所以简略的权重初始化N(0, 0.02)就满足了。
  • 数据输入:咱们运用了一个BPE词汇表,其间包含40,000个merges
  • 练习设置:运用batchsize为64,seq长度为512的随机采样数据,练习了100个epochs

GPT2-扩大数据和模型参数

We would like to move towards more general systems which can perform many tasks – eventually without the need to manually create and label a training dataset for each one.

牢记这一点,才干更好的运用LLM

GPT-2 vs GPT-1的模型修正

  1. 从一开端的表中咱们看到GPT-2比较GPT-1有显着的模型参数上升。

  2. 由于此刻现已期望GPT-2是一个通才,能够zero-shot处理下流使命,所以构建了新的练习数据,清洗得到约40G

  3. GPT-2依据GPT-1做了一些修正:

    1. 将Layer Normalization移动到了每一个sub-block的前面(我乃至置疑过,但后来发现官方开源代码确实是这样的 hhh
    2. 在最后的self-attention block之后添加了一个额定的layer normalization
    3. 考虑到模型深度造成的残差堆集,对初始化做了修正。在初始化时,经过一个因子 1/√N 对残差层的权重进行缩放,其间 N 是残差层的数量。
    4. 将词汇表扩展到50,257个词汇。
    5. 上下文长度从512添加到了1024
    6. 练习batchsize添加到了512

GPT-3

论文中号称运用与GPT-2相同的模型和架构,包含其间描绘的修正初始化、预归一化和可逆符号化。

在transformer的层中,咱们运用了Sparse Transformer,transformer中是用了alternating dense & locally banded sparse attention patterns。

手把手复现一个ChatGPT

表2.1显示了8个模型的巨细和架构。其间,nparams是可练习参数的总数,nlayers是总层数,dmodel是每个bottleneck layer中单元的数量(咱们始终使feedforward layer的巨细是bottleneck layer的4倍,d_ff = 4 * d_model),dhead是每个留意头的维数。一切模型都运用n_ctx = 2048个符号的上下文窗口。

咱们将模型沿着深度和宽度维度分配到 GPU 上,以最小化节点之间的数据传输。 每个模型的精确架构参数是依据核算功率和在GPU布局中的负载平衡而挑选的。从前的作业标明,在合理的规划内,validation loss对这些参数并不敏感。

至于本文很重要的作业,比方in-context learning,则与模型练习没有联系。

GPT-4****

Multimodal model:image & text->model->text。~~~~chatgpt~~~~的完结暂时不需求~~~~GPT-4~~~~。

代码完结

至此,咱们就了解了一个Pre-train LLM的一些根本模型细节。GPT1和2在前几年都有许多许多开源完结,很有利于咱们了解学习。

由于当时没有满足的机器和数据,所以能够依据此跑一个小数据的小模型,这就让我想起最初试验Bert的时分了:zhuanlan.zhihu.com/p/113326366…

NOTE:当时由于 torch 和huggingface的存在,导致咱们在实践编码上变简略了许多,许多细节自己抄一遍会更有感觉。

依据上面咱们说到的技能道路,能够很清晰的将整个进程进行完结。在一个模型练习界说的时分,咱们一般会将其分为几个模块:

model.py:模型界说
train.py:模型练习调用代码
inference.py:模型推理调用代码
conf.py:保存公共装备信息,比方全局随机种子等
data:文件夹下包含数据预处理代码和一些常用demo数据,由于不同数据源格局不同,会有多个数据处理文件的或许。练习全量数据一般也能够保存在这儿,只是不会放在github上。(PS:关于常用数据,能够保存在公共目录下,便利不同项目运用)
bash:文件夹下保存多种常用bash文件,能够直接运转
其他可选:依据模型复杂度等,有或许会拆分不同的代码文件来保存模型用到的公共代码

下面咱们解释一下BPE和transformer_block

BPE

这部分简化完结仿制自:www.cnblogs.com/wwj99/p/125… platform.openai.com/tokenizer

举例来说明,咱们要对下面的字符进行编码。

aaabdaaabac

字节对 aa 呈现的次数最多,所以咱们将它替换成一个没在字符串中被用过的字符 Z

ZabdZabac
Z=aa

然后咱们重复这个进程,用 Y 替换 ab

ZYdZYac
Y=ab
Z=aa

持续,用 X 替换 ZY

XdXac
X=ZY
Y=ab
Z=aa

这个进程重复进行,直到没有字节对呈现超过一次。当需求解码时,就将上述替换进程反向进行。

github.com/openai/tikt…

bpe的优势,是能够将vocab size 变多,可是每次输入就能够变短,这也是模型能够处理越来越长token的一种办法。坏处在于了解起来需求一个转化本钱。一起这种操作能够防止OOV的问题。

实践运用中,能够用tiktoken来完结。

tiktok…tiktok…tiktok->tiktoken hhh

Transformer_block

关于self-attention和transformer的介绍也是比较古老了,这儿我仿制一下自己之前给Bert的介绍,做一些修正

数据输入:GPT用了两种输入进行相加输入到模型中:词向量参数,方位向量参数。并且方位向量的参数是可学习的。

一个transformerblock中包含了masked-multi-head-self-attention和feedforward,以及穿插在他们之间的normalization或许dropout等。下面咱们介绍一下multiheadattention和feedforward。至于mask,在这个decoder-only的结构中,咱们首要是用其来盖住当时timestep中后边的词语,不让解码器看到future words,防止用答案预估答案。

Multi-heads self-attention与feedforward

手把手复现一个ChatGPT

左边的图是一个self attention。即一个head,右边是多个head。

从结构中能够看到,Q,K,V便是咱们输入的三个(句子词向量),从之前的词向量剖析可知,输出向量巨细从len -> len x hidden_size,即len x 768。

假如是self-attention,Q=K=V,假如是一般的attention,Q !=(K=V)。

前次说过了Q和K便是两个要比较的东西。在NLP中的attention中K和V一般是相同的【或许能够修正V得到一种新的attention,应该有了相似的研讨了吧】

可是,不管用的是self-attention仍是一般的attention,参数核算并不影响。由于在输入单头head时,对QKV的向量均进行了不同的线性变换,引入了三个参数,W1,W2,W3。其维度均为:768 x 64。为什么是64呢,

768/12 . 挑选了12个头。所谓的12个头便是有12个scaled dot-product attention. 所以每个头的输入都是64维的。 得出:W1,W2,W3的维度都是768 x 64。为什么W123是相同的维度,由于他们的输入是相同的,自身便是同一个东西在做self-attention。 所以参数核算成果为:

一个head:768 * 768/12 * 3 输入768,自身维度的输入是768,输出是64,有三个

有12个head。所以有768 * 768/12 * 3 * 12

有一个线性改变的W,768 * 768

所以在multi-heads的参数量为768 * 768/12 * 3 * 12 + 768 * 768=1769472+589824=2359296

对应的每个W都会有一个相应维度的bias 12个 64 以及一个 768.合起来便是两个768

FeedForwrd:

FFN(x)=max(0,xW1+b1)W2+b2FFN(x)=max(0, xW_1+b_1)W_2+b_2

看到参数有W1,W2。其间用到了两个参数W1和W2,Bert沿用了惯用的全连接层巨细设置,即4 * dmodle(768,上一层的输出,x便是768 * 1),为3072,因而,W1,W2巨细都是为768 * 3072,2个为 2 * 768 * 3072=4718592。

有两个bisa,那么巨细为3072*2

解释一下,W1担任把768变成3072, W2再变回来。BTW这个max便是relu。

参阅完结如下:

class CausalSelfAttention(nn.Module):
    def __init__(self, config):
        super().__init__()
        assert config.n_embd % config.n_head == 0
        # key, query, value projections for all heads, but in a batch
        self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd, bias=config.bias)
        # output projection
        self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=config.bias)
        # regularization
        self.attn_dropout = nn.Dropout(config.dropout)
        self.resid_dropout = nn.Dropout(config.dropout)
        self.n_head = config.n_head
        self.n_embd = config.n_embd
        self.dropout = config.dropout
        # flash attention make GPU go brrrrr but support is only in PyTorch >= 2.0
        self.flash = hasattr(torch.nn.functional, 'scaled_dot_product_attention')
        if not self.flash:
            print("WARNING: using slow attention. Flash Attention requires PyTorch >= 2.0")
            # causal mask to ensure that attention is only applied to the left in the input sequence
            self.register_buffer("bias", torch.tril(torch.ones(config.block_size, config.block_size))
                                        .view(1, 1, config.block_size, config.block_size))
    def forward(self, x):
        B, T, C = x.size() # batch size, sequence length, embedding dimensionality (n_embd)
        # calculate query, key, values for all heads in batch and move head forward to be the batch dim
        q, k, v  = self.c_attn(x).split(self.n_embd, dim=2)
        k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
        q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
        v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
        # causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)
        if self.flash:
            # efficient attention using Flash Attention CUDA kernels
            y = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=self.dropout if self.training else 0, is_causal=True)
        else:
            # manual implementation of attention
            att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
            att = att.masked_fill(self.bias[:,:,:T,:T] == 0, float('-inf'))
            att = F.softmax(att, dim=-1)
            att = self.attn_dropout(att)
            y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
        y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side
        # output projection
        y = self.resid_dropout(self.c_proj(y))
        return y
class Trans_Block(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.ln_1 = LayerNorm(config.n_embd, bias=config.bias)
        self.attn = CausalSelfAttention(config)
        self.ln_2 = LayerNorm(config.n_embd, bias=config.bias)
        self.mlp = MLP(config)
    def forward(self, x):
        x = x + self.attn(self.ln_1(x))
        x = x + self.mlp(self.ln_2(x))
        return x

第二节:增量学习与微调

增量学习

假如咱们从头开端练习,显着本钱过高,所以咱们能够退而求其次,进行continue leanring。增量学习,有时分也叫做持续学习,是依据一个模型的进一步学习。在LLM场景下,其意图往往是进一步增强LLM的根底才干。学习办法也与预练习根本共同,加载好之前练习的模型,然后持续学习。在练习技巧上,有时分会有一些练习设置上的改变,要依据实践情况而定。

与Finetuning比较,CL在界说上愈加倾向于轻意图倾向性,着重优化模型的通用言语才干

留意,在数据输入和模型设置上有必要与原始模型相同。这部分的练习代码装备和微调根本共同,能够拜见微调部分

微调finetune(代码需求调整)

所谓微调,与增量学习相同,即依据给定模型进行进一步学习。与增量学习上的仅有差异在于,其往往会有更强烈的范畴/使命意图性。

微调的流程是从一个现已练习好的模型加载,然后开端练习,在练习设置时,一般以较小的学习率,这是为了防止对全体的大模型进行波动干扰,影响到原始的通用才干。一起练习所运用的数据往往会与自己的意图有关,比方某个垂直范畴,或许某种办法的对齐(例如iGPT或许cGPT)

下面是m1笔记本的练习代码示例:

m1
练习
python train.py config/train_shakespeare_char.py --device=cpu --compile=False --eval_iters=20 --log_interval=1 --block_size=64 --batch_size=12 --n_layer=4 --n_head=4 --n_embd=128 --max_iters=100 --lr_decay_iters=2000 --dropout=0.0
推理
python sample.py --out_dir=out-shakespeare-char --device=cpu

Supervised fine-tuning

GPT1在实践运用到下流使命的时分,需求进行fine-tuning来优化作用,了解即可,上个年代的产品。在后边了解对齐使命的时分,这部分了解有帮助。

在榜首部分的言语模型练习好之后,接下来进行监督学习。给定一个有标签的数据集C,X是输入token,y是label。

输入X经过预练习模型得到终究的transformer block的激活函数hlmh_l^m,之后将其喂入一个线性输出层(参数为W_y)中来预估label y

P(y∣x1,…,xm)=softmax(hlmWy)P(y|x^1,…,x^m)=softmax(h_l^mW_y)

依据此,最大化以下方针函数

L2(C)=∑x,ylogP(y∣x1,…,xm)L_2(C)=\sum_{x,y}logP(y|x^1,…,x^m)

这儿也说到了,将言语模型作为一个额定的方针进行优化能够提高监督学习模型的泛化性,加快收敛。

L3(C)=L2(C)+∗L1(C)L_3(C)=L_2(C)+\lambda*L_1(C)

手把手复现一个ChatGPT

alignment(对齐)-SFT

依据上面的介绍,咱们了解了怎么进行模型的finetuning。一起OpenAI有清晰的提出,对齐是让模型了解到人类的习气,思维,说话办法和价值观。这儿咱们要让模型来学习人类针对一些prompt的回复,相同经过SFT。

那么练习数据应该怎么结构?先看一下在预练习的时分数据应该怎么结构

def get_batch(split):
    """
    GPT生成~
    这段代码界说了一个函数get_batch(split),用于生成输入数据(x)和方针数据(y)的小批量样本。
    首要,依据split参数挑选运用练习集仍是验证集的数据。假如split是'train',则运用练习集数据,否则运用验证集数据。
    然后,经过随机生成一个长度为(batch_size,)的整数张量ix,其取值规划是从data数据中减去block_size的长度。
    接下来,经过将索引ix运用于data,运用列表推导式生成输入数据x和方针数据y。具体而言,关于每个索引i,从data中截取长度为block_size的子序列作为输入x,从索引i+1到索引i+block_size+1的子序列作为方针y。
    最后,将生成的x和y转移到设备(通常是GPU)上,并返回这对数据作为函数的输出。
    这个函数的意图是为了生成模型练习进程中的小批量样本,其间batch_size标明每个批次的样本数量,block_size标明输入和方针序列的长度。
    """
    # generate a small batch of data of inputs x and targets y
    data = train_data if split == 'train' else val_data
    ix = torch.randint(len(data) - block_size, (batch_size,))
    x = torch.stack([data[i:i+block_size] for i in ix])
    y = torch.stack([data[i+1:i+block_size+1] for i in ix])
    x, y = x.to(device), y.to(device)
    return x, y

ok这段代码也很简略,依据此,咱们考虑一下alignment的数据应该怎么结构,下面是找的一段开源的prompt指令练习数据:

{"instruction": "在以下文本中提取一切的日期。", "input": "6月21日是夏至,这是一年中白日最长的一天。", "output": "6月21日"}
{"instruction": "", "input": "请生成一个新闻标题,描绘一场正在产生的大型自然灾害。\n\n", "output": ""强烈飓风肆虐,数百万人疏散!""}

关于一个生成模型来说,他的输入应该是指令和input,输出便是output,由于根底的生成才干咱们其实并不需求在这个进程中练习,所以咱们就会有清晰的练习方针:即依据给定的指令+input生成output。依据此,练习数据的生成代码能够是以output的结尾idx为符号开端结构预估方针。

def get_instruction_batch(split):
    # generate a small batch of data of inputs x and targets y
    data = train_data if split == 'train' else val_data
    ix = torch.randint(low=data.find("output") + 11, len(data) - block_size, (batch_size,))
    x = torch.stack([data[i:i+block_size] for i in ix])
    y = torch.stack([data[i+1:i+block_size+1] for i in ix])
    x, y = x.to(device), y.to(device)
    return x, y

可是咱们想一想,似乎在运用chatgpt的时分,它会主动补全咱们的一些没有写完或许没有写对的指令,所以这儿的练习是能够直接保留一开端getbatch的数据结构代码的。

【绕了一下~】

至此,能够说就能够得到一个像模像样的chatGPT了。

第三节:RLHF:RM与PPO

至此,就现已完结了一切的预练习+SFT的作业,也便是说咱们现已得到了一个初步可用的模型,也能够说就能够得到一个像模像样的chatGPT了。假如是依据开源模型微调的话,这个模型的作用或许现已和许多开源的模型作用适当,由于当时开源的作业也没有清晰标明他们做过后续的RM和PPO的作业。

NOTE:为什么咱们这儿要用RL来学习用户反应而不是持续用SFT呢?由于SFT的持续学习,鉴于方针函数,其依然是在学习一个概率,不同的数据是在改动概率散布。但咱们这儿的方针是进一步学习alignment。假如持续堆积SFT,无非便是让模型输出的习气和内容愈加契合“人话”,即言语模型自身的功能会越来越好。

而RM+PPO想要做的是让模型学习在N句都是人话的选项中,排序出哪些更好。比较SFT照葫芦画瓢,此刻其间隐含的是一种价值观、事实性以及labeler偏好的判断,会让模型更不简略胡言乱语。

SFT:偏重学习给定prompt下的言语表达办法

RLHF:偏重学习给定prompt下的言语偏好

在依据给定模型finetune的进程中,假如不断着重SFT的学习,或许会让RLHF功能下降;此刻合理的情况应该是SFT和RLHF一同作用来完结对模型的迭代优化,乃至仅RLHF。

ref:www.youtube.com/watch?v=hhi…

流程

简略介绍一下这部分的作业流程,下图中的step2和3:

手把手复现一个ChatGPT

  1. 搜集对比数据,练习一个奖赏(Reward)模型

    1. 采样获取propmt和几个言语模型输出的成果,构成pair
    2. 标示者对这些输出成果进行从好到坏的排序
    3. 运用标示数据来练习奖赏模型
  2. 依据RW模型,运用RL对言语模型进行优化

    1. 从练习数据中采样一个prompt
    2. 言语模型针对这个prompt输出成果
    3. 奖赏模型为这个输出的成果核算一个分数
    4. 言语模型运用这个核算出来的分数进行模型优化

从上面的流程中,咱们能够看到:依据此再进行后续的作业,RW+PPO,则是将对人类的依靠开释,运用RW+PPO不断进行模型优化。下面咱们别离介绍奖赏模型、PPO算法以及RLHF的流程与建模。

Reward模型练习

为了完结RM+PPO,首要咱们需求练习一个Reward模型,练习数据的格局:(prompt, winning_response, losing_response)。

  • Prompt(提示): 标明输入的问题或上下文。

  • Winning response(获胜答复): 标明模型认为是正确或优异的答复。

  • Losing response(失利答复): 标明模型认为是错误或较差的答复。

  • 数据规划:10万到100万个example

    • InstructGPT:50,000个prompt。每个prompt有4到9个response,构成了6到36个(winning_response, losing_response)pair。这意味着在(prompt, winning_response, losing_response)格局中有30万到180万个练习示例。
    • Constitutional AI,或许是Claude(Anthropic)的backbone:318,000个comparison pair(其间13.5万个由人类生成,18.3万个由人工智能生成)。Anthropic还有一个旧版别的数据开源(hh-rlhf:huggingface.co/datasets/An… pair。

练习设置:

r:被练习的奖赏模型,由参数进行参数化。练习进程的方针是找到使丢失最小化的。

  • 练习数据的格局如下:

    • x:提示(prompt)
    • yw:获胜答复(winning response)
    • yl:失利答复(losing response)
  • 关于每个练习样本(x,yw,yl):

    • sw = r(x, yw):获胜答复的奖赏模型得分
    • sl = r(x, yl):失利答复的奖赏模型得分
    • 丢失值的核算公式为:−log((sw−sl))
  • 方针是找到参数,以最小化一切练习样本的期望丢失,即−Exlog((sw−sl))。

这儿咱们的奖赏模型能够依据之前练习好的SFT进行,即参阅GPT-1年代介绍过的finetune作业,来完结对练习样本的打分。

PPO算法

PPO是OpenAI推出的RL算法,其提出的意图是为了处理Policy Gradient中低效与更新不稳定的问题,具体特色如下:

  1. Mini-batch training:由on-policy修正成为off-policy,能够提高对受限数据集的运用功率
  2. Regularization KL:PPO运用了KL作为束缚来防止对小型数据集的过拟合
  3. Clip Objective:运用了clip来防止不稳定的改变,也削减了过拟合的风险

PPO在chatGPT中作用的节点如下

手把手复现一个ChatGPT

关于上图中左边绿色的部分,严厉意义上应该是一个指令微调后的模型,假如咱们直接用RL进行模型调整的话,一方面不必定能够很好的针对给定的prompt生成想要的格局,特别是一个general的LLM要应对对话这种办法,另一方面功率这样的调整功率也不够高。SFT与RL实践要做的作业的偏重仍是不同的,上面咱们现已剖析过了。

Meta与CMU也放出了相应的研讨,即使没有经过RLHF的练习,只是经过具体的SFT也能够拿到很好的作用。论文见:arxiv.org/pdf/2305.11…

关于PPO具体介绍能够看论文,也推荐这个视频进行了解:www.bilibili.com/video/BV1sg…

RLHF

这儿回顾一下SFT的流程:从prompt数据会集采样prompt,然后由标示人员进行标示,终究运用prompt和标示成果构成的数据来finetune言语模型。

综上,整个流程中标示人员参加的环节有SFT和RW模型练习的环节。也便是在线上实践运用模型的时分,SFT和RW模型练习这两个环节是能够将用户反应的信息引入并进行模型优化的。

练习流程

这儿,咱们进一步练习SFT模型,生成的输出答复将最大化奖赏模型的得分。OpenAI运用Proximal Policy Optimization (PPO)进行这部分的练习。在此进程中,随机挑选一批prompt,将每个prompt输入到LLM中,得到一个答复,并由RM给出得分,依据给定的得分进行模型参数更新。

从这个阶段得到的模型不应该与SFT阶段得到的模型相差太远,这一点鄙人面的公式中以两个模型的KL散度作为束缚条件体现。其原因是关于任何给定的prompt,有许多或许的答复,其间绝大部分奖赏模型曾经从未见过。关于那些不知道的(prompt,answer)对中的许多情况,奖赏模型或许会错误地给出极高或极低的得分。假如没有这个束缚条件,咱们或许会倾向那些得分极高的答复,虽然它们或许不是好的答复。

强化学习建模

  • 动作空间(Action space):LLM运用的词汇表中的token。履行动作意味着完结一次要生成的token的挑选。
  • 观察空间(Observation space):一切或许的prompt的散布。
  • Policy:给定一个observation(prompt)下的一切或许采纳的action的概率散布。LLM便是这个Policy,由于它会决策一个token有多大的或许性被挑选。
  • 奖赏函数(Reward function):奖赏模型,就方才咱们讲过的reward模型练习
  • 练习数据:随机挑选的prompt。
  • 数据规划:10,000 – 100,000个prompt(InstructGPT:40,000个prompt。)

数学标明

  • RM: 奖赏模型

  • LLM^SFT: SFT的成果,有监督微调模型。

    • 给定prompt x,它输出一系列答复的散布。
    • 在InstructGPT论文中,LLM^SFT被标明为SFT,这是由于在强化学习的建模中常常这样标明
  • LLM^RL_: 运用强化学习练习的模型,由参数进行参数化。

    • 方针是找到使得依据RM得分最大化的。
    • 给定prompt x,它输出一系列答复的散布。
    • 在InstructGPT论文中,LLMRL被标明为RL。原因同上
  • x: prompt

  • D_RL: 清晰用于RL模型的prompt散布。

  • D_pretrain: 预练习模型的练习数据散布。

关于每个练习进程,从D_RL中抽取一个x_RL批次,从D_pretrain中抽取一个x_pretrain批次。每个样本的方针函数取决于样本来自哪个散布。

x_RL = prompt_batch

x_pretrain = batch of seqs

具体练习进程如下:

  1. 关于每个x_RL(即prompt),运用LLM_RL生成回复y。objective方程如下,公式第二项是KL散度,意图是为了不让RL练习后的模型与SFT差异过大。

objective1(xRL,y;)=RM(xRL,y)−log(LLMRL(y∣x)/LLMSFT(y∣x))objective1(x_{RL},y;)=RM(x_{RL},y)−log(LLM^{RL}_(y|x)/LLM^{SFT}(y|x))

  1. 关于每个x_pretrain,方针函数的核算如下。从直观上讲,这个方针是保证RL模型在文本完结使命上体现不会比预练习模型更差。

objective2(xpretrain;)=log(LLMRL(xpretrain)objective2(x_{pretrain};)=log(LLM^{RL}_(x_pretrain)

在这儿,x_pretrain标明预练习数据会集的样本(例如,预界说的对话数据)。方针函数objective2的核算触及运用LLMRL模型对xpretrain进行采样,并核算其对数概率。

经过最大化这个方针函数,咱们期望保证RL模型在文本完结使命上的体现不会比预练习模型更差。这有助于坚持模型的根本才干,并防止在优化进程中产生负面作用。经过操控方针函数中的参数,能够调整这个使命对优化进程的相对重要性。

  1. 终究的方针是以上两个公式之和。在RL设置中,最大化objective作为咱们的学习方针

objective()=ExDRLEyLLMRL(x)[RM(xRL,y)−log(LLMRL(y∣x)/LLMSFT(y∣x))]+ExDpretrainlog(LLMRL(xpretrain)objective()=E_{x~D_{RL}}E_{y~LLM_^{RL}(x)}[RM(x_{RL},y)−log(LLM^{RL}_(y|x)/LLM^{SFT}(y|x))]+E_{x~D_{pretrain}}log(LLM^{RL}_(x_pretrain)

Tips:已知当时依据RLHF的思路和流程,但实践作用不必定是最优的,这个情况在OpenAI的WebGPT中也相同有所评论

第四节:微调的要害要点

在咱们的实践开发和运用进程中,大多数人的作业都是依据开源模型的微调优化;即使是当时一些开源出来的模型,也有许多模型基底是依据开源进行的,毕竟大把的时间和金钱,仍是能省则省。这一节,咱们评论一些盛行的微调办法以及并对各个模块的优化点进行整理,并将数据相关作业也做了简略评论。

开源基底模型

当时业界开源模型首要由外国研讨者贡献,国内当时开源较为有代表性的有复旦的MOSS,清华的chatGLM以及近期炽热的RWKV等作业。

在前序内容中咱们介绍了GPT系列作业的架构,这也是当时大多数模型的架构。由于之前的文章现已提供过了一些根底的模型介绍,www.zhihu.com/question/59…

在之前介绍的根底上,新加两个作业:

  1. ColossalChat:首个开源了全体的RLHF-pipeline的作业。github.com/hpcaitech/C…
  2. Linly:身边小伙伴的作业,全体开源的信息相对清晰。github.com/CVI-SZU/Lin…

微调手法

本节图片首要来自于chatGLM的微调教程PPT。

全参数微调

所谓全参数微调,便是对模型的一切参数都进行了调整,也是上文中说到的SFT的完结办法。由于在实践中存在客观困难(模型大,数据多,参数多)等,所以咱们有一些优化的微调手法。

混合精度微调: 混合精度是指练习时在模型中一起运用 16 位和 32 位浮点类型,然后加快运转速度,削减内存运用的一种练习办法。经过让模型的某些部分坚持运用 32 位类型以坚持数值稳定性,能够缩短模型的单步用时,而在评价目标(如准确率)方面仍能够获得平等的练习作用。现代加快器运用 16 位 dtype 履行运算的速度更快,由于它们有履行 16 位核算的专用硬件,并且从内存中读取 16 位 dtype 的速度也更快。实践作业中要留意的点:

  • 实践作业中,常常在内存中用fp16做贮存和乘法然后加快核算,用fp32做累加防止下溢出误差
  • 为了防止梯度过小,能够手动在防止梯度爆破的根底上,在梯度上进行扩大,这样当实践产生fp16和fp32的核算的时分,由于咱们扩大了值,就不简略呈现下溢出。
  • 小结: 显存 ,加快练习速度,可是会丢失精度

手把手复现一个ChatGPT

多卡练习

Data Parallel:多张卡运用相同的模型,一张卡跑一部分batch,各自bp核算gradient,然后全体求均值,然后各自进行优化然后进行参数更新,这样的优点是速度比较快,通讯很少。缺陷是浪费了memory,由于每张卡都要load相同的模型,都要核算一次gradient。一起假如模型太大,单卡太小也放不进去。

Model Parallel:将一个模型拆分到不同的卡上,运用同一个batch的数据,在实践练习的时分将中心成果在卡与卡之间移动,别离练习模型的不同部分。长处是克服了上面说的缺陷,但由于这种频频的通讯,也导致了全体的核算功率的问题。

ZeRO: ZeRO(The Zero Redundancy Optimizer)是一种用于大规划散布式深度学习的新式内存优化技能。ZeRO能够在当时的GPU集群上练习具有1000亿个参数的深度学习模型,其吞吐量是当时最佳系统的三到五倍。它有三个优化进程,别离对应Optimizer State Partitioning, Add Gradient Partitioning以及Add Parameter Partitioning。简略来说便是将每个进程都拆分到不同的卡上,比较baseline(data parallel), GPU能够节省许多。

手把手复现一个ChatGPT

P-tuning

手把手复现一个ChatGPT

P-tuning v1 首要结构是运用了一个prompt encoder(例如BiLSTM+MLP),将prompt 经过encode得到向量,然后再与input embedding进行拼接,具体拼接方位不必定是前缀,也能够是中心方位。其初衷是为了添加模型的了解才干。

P-tuning v2 则是一个改善,不只会添加prompt的embedding调整,还会对应在每一层中都加上prompt对应的权重参数,然后对这部分进行调优(实践完结中有一种很好了解的办法,便是要额定拼接一部分参数,但这种生效办法很不优雅,能够拜见chatGLM中的代码完结huggingface.co/THUDM/chatg…

以及苏剑林在P-tuning评论过的完结办法zhuanlan.zhihu.com/p/364141928…

P-tuning v2这种办法,或许会有灾难性忘记以及过拟合的问题,由于其往往用了比较少的数据(这也是其长处)完结高性价比的finetune。

LoRA

LoRA(Low-Rank Adaptation)是一种用于对大型模型进行低本钱微调的办法。根本原理是冻住预练习好的模型权重参数,在冻住原模型参数的情况下,经过往模型中加入额定的网络层,并只练习这些新增的网络层参数。由于这些新增参数数量较少,这样不只 finetune 的本钱显着下降,还能获得和全模型微调相似的作用。

手把手复现一个ChatGPT

当咱们练习大型的言语模型时,它通常有非常多的参数,这使得练习进程非常耗时和贵重。LoRA办法的核心思维是,这些大型模型其实是过度参数化的,其间的参数改变能够被视为一个低秩矩阵。因而,咱们能够将这个参数矩阵分化成两个较小的矩阵的乘积。在微调进程中,咱们不需求调整整个大型模型的参数,只需求调整低秩矩阵的参数。这样做能够显着削减微调所需的参数数量,然后降低练习本钱。

具体来说,咱们假定大型模型的参数矩阵A能够分化为两个较小矩阵U和V的乘积:A = UV。咱们将U和V看作是微调的参数,而A是固定的。实践作用中,前向传播时A和UV都会核算作用,将终究的成果相加,然后进行丢失核算。接下来进行反向微调,此刻只是调整UV即可。经过调整U和V的数值,咱们实践上是在微调整个模型。一旦咱们调整好了矩阵U和V的数值,能够将U和V的乘积作为一个低秩矩阵的近似,记作UV^T。然后,咱们将UV^T与原始参数矩阵A进行加法操作,即 A_new = A + UV^T。终究用A_new替换掉A,就得到了新的调整后的模型

下图中的W0便是A,delteW便是UV。

手把手复现一个ChatGPT

这种低秩矩阵的分化办法能够有效地削减微调所需的参数量,由于U和V的巨细较小。这使得微调进程愈加高效和经济。一起,由于大部分参数是固定的,咱们能够更快地完结微调进程。

chatGLM推出了微调的教程,拜见:www.bilibili.com/video/BV1fd…

优化点

除了微调以外,还有许多当地能够优化,简略列举如下

练习优化:当时的练习本钱依然很高,怎么能够高效省钱的完结练习,除了练习中的一些tricks以外,软硬件结合等相关的操作或许也会对未来的微调有所影响。

模型优化:模型结构上咱们依然存在优化点,比方当时的transformer是最优的的么,拜见RWKV:github.com/BlinkDL/RWK…

推理优化:当时现已有许多人在研讨怎么在消费级显卡上进行推理,也有人研讨进行端上的布置,这块怎么在保证模型作用的根底上,完结低本钱布置则是会很大程度上影响到模型终究的运用和落地,以及未来运用场景的拓宽。

tokenizer优化:在不同的言语中,不同的tokenizer或许会有不同的作用;一起 tokenizer的存在是否是满足有价值的,也是当时被诟病的一点(twitter.com/karpathy/st…

手把手复现一个ChatGPT

生成速度优化:从左往右的生成具有速度上的问题,能够拜见arxiv.org/pdf/2205.07… 字节跳动DA-transformer在生成上做到了加快。

上下文长度:Vcc: Scaling Transformers to 128K Tokens or More by Prioritizing Important Tokens将上下文长度做了延伸 arxiv.org/abs/2305.04…

数据

练习数据非常重要,能够说是决议了模型功能的天花板,这一点现已被一切人公认了。可是当时有个要害点是数据、模型和作用三者之间的联系暂时还无法做出具体建模。有研讨指出当时GPT的模型其实还没有被完全练习到,当时运用的练习数据还不满足。而数据(规划,质量)要怎么与模型结构以及参数搭配才干得到最好的作用(还有具体的ROI上的问题),这块的研讨暂时还没有结论,也是一个非常有前(需求钱)景的研讨方向。

关于数据这块,相信也是各个组织都在认真考虑和研讨的方向,当时关于数据的许多问题其实也没有清晰的答案,也处在一个评论的阶段:

  • 预练习阶段:树立言语模型的根底通用才干,也是最耗费算力和数据的部分。

    • 要怎么对数据进行清洗,比方数据去重、数据normalization等操作。
    • 数据质量:怎么衡量数据的质量,比方红楼梦等四大名著和当代一篇长文网络小说,古诗词和现代口水歌之间的好坏。
    • 数据规划,在算力有限的情况下,数据规划应该怎么挑选
  • SFT+RLHF:

    • 数据标示机制怎么规划才干提高数据质量
    • 有没有一些trick的思路来获取数据
    • 增量数据或许finetune的数据要怎么与trained模型结合
  • 数据主动合成与寻觅:针对一个模型,跟着练习的进行,能够学习的数据会越来越少;而模型练习者需求清晰了解模型的才干,然后帮助其寻觅到对它有用的数据。这个进程理论上是能够主动化进行的。

  • 评价数据:怎么规划评价数据,怎么获取评价数据,评价维度等,由于这个方向除了数据还会触及到对模型评价的维度或许具体的评价规范的规划问题,所以不单是一个简略的数据问题。这儿能够见当时

  • 部分已知开源数据:严厉意义上,咱们需求将数据分为预练习数据集和对齐数据集两部分。具体的开源数据有许多,限于精力这儿就不穷举了,很简略能够找到许多。

    • 预练习数据:这部分数据相对较多,但由于算力的问题,合理进行数据过滤和挑选依然是要害问题。常见的开源数据来源有维基百科、Common Crawl等;而关于推理才干等方面则需求一些开源的代码/科学推理等相关的数据运用。

    • 对齐数据:这儿咱们更想要的是指令数据集,例如huggingface.co/datasets/An…

    • 中文开源项目:

      • 悟道数据:data.baai.ac.cn/details/WuD…

      • Linly:github.com/CVI-SZU/Lin…

      • Moss 数据:github.com/OpenLMLab/M…

      • CLUE:github.com/CLUEbenchma…

第五节:小结(全体的review)

这个内容能够分为三大块,榜首节与第二节归于大言语模型的根底知识,具有这些逻辑上就能够自己练习得到一个LLM;第三节则是对RLHF进行了拆解,这是chatGPT防止“胡言乱语”重要的一个进程,但也是当时不太好评价作用的一个维度;第四节则是给出了微调的要点,也是当时要开展模型相关作业时分有必要了解的内容。

本文没有具体介绍的包含:

  1. 数据搜集与校验机制:机制上怎么获取数据、标示数据质量怎么从机制上优化、数据清洗trick,数据搜集和处理pipeline
  2. RLHF完结细节:Reward模型练习与依据PPO进行LLM优化的完结细节。
  3. 工程优化相关细节。

关于文中说到的榜首第二末节的代码见:github.com/DukeEnglish…

后续TODO:

  1. 添加依据中文数据的模型练习和微调
  2. 添加RLHF的具体完结
  3. 以某个开源中文模型为例进行上述流程测验

附录

原始gpt-1代码:github.com/openai/fine… (手动完结了transformer,可是依靠了旧版tensorflow)

原始gpt-2代码:github.com/openai/gpt-…

MAC环境装备能够参阅:zhuanlan.zhihu.com/p/548685817

当时咱们这个行业也存在一个问题,咱们代码和论文有时分不知道是成心仍是不小心,会有一些版别不重合(就像各位的注释、文档和代码的不共同相同,嘿嘿 手动狗头)这导致的是在学习的时分,搞不明白代码为啥这样写,为啥跟论文写的不太相同,并且不相同的当地又很少……。

参阅

本文重点参阅内容如下,感谢大佬们的分享:

www.bilibili.com/video/BV1tm…

www.jiqizhixin.com/articles/20…

www.cnblogs.com/wwj99/p/125…

www.youtube.com/watch?v=kCc…

www.qbitai.com/2023/01/414…

medium.com/pytorch/col…

github.com/Morizeyao/G…

huyenchip.com/2023/05/02/…

www.zhihu.com/column/c_14…

arxiv.org/pdf/2009.01…

www.bilibili.com/video/BV1fd…

yaofu.notion.site/C-Eval-6b79…

blog.csdn.net/qq_41771998…