还有完整技术介绍

Pine 发自 凹非寺

量子位 | 公众号 QbitAI

现在只用60行代码,就能从0构建GPT了!

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

想当初,前特斯拉前AI总监的minGPT和nanoGPT也都还要300行代码。

这个60行代码的GPT也有名字,博主将它命名为PicoGPT

不过和此前minGPT和nanoGPT的教程不同,今天要讲的这个博主的教程,更侧重于代码实现部分,模型的权重则用已经训练好的。

对此,博主解释称这篇教程的重点在于提供一个简单且易于破解的完整技术介绍

这对还不理解GPT背后概念的盆友,算是非常友好了。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

还有网友称赞,这篇博客介绍得非常清晰,第一部分尤为如此。

这篇介绍GPT模型的文章太好了,它比我之前看到的介绍都要清晰,至少在第一部分讨论文本生成和取样是这样的。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

目前,此项目在GitHub上标星已破百,HackerNews上的点击量也即将破千。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

从GPT是什么讲起

在介绍之前,还是需要说明一下,这篇教程不是完全零门槛,需要读者提前熟悉Python、NumPy以及一些基本的训练神经网络。

教程的重点聚焦在技术介绍上,统共有六大部分:

什么是GPT?

按照惯例,在正式构建GPT之前得先对它做一些基本介绍,教程从输入/输出、生成文本以及训练三个部分分别来讲GPT是如何工作的。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

在这趴,博主附上代码,甚至还用了一些比喻来让读者们更好地理解GPT。

举个栗子

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

,在输入这一部分,作者将句子比作一条绳子,tokenizer则会将其分割成一小段一小段(单词),被称作token。

又比如说,在生成文本这part介绍自动回归时,博主直接贴上代码:

def generate(inputs, n_tokens_to_generate):
for _ in range(n_tokens_to_generate): # auto-regressive decode loop
output = gpt(inputs) # model forward pass
next_id = np.argmax(output[-1]) # greedy sampling
inputs = np.append(out, [next_id]) # append prediction to input
return list(inputs[len(inputs) – n_tokens_to_generate :]) # only return generated ids
input_ids = [1, 0] # “not” “all”
output_ids = generate(input_ids, 3) # output_ids = [2, 4, 6]
output_tokens = [vocab[i] for i in output_ids] # “heroes” “wear” “capes”

在每次迭代中,它会将预测的token追加回输入,这个预测未来值并将其添加回输入的过程就是GPT被描述为自动回归的原因。

60行代码怎么运行?

了解完GPT的基本概念之后,就直接快进到了如何在电脑上运行这个PicoGPT。

博主先是甩出了他那只有60行的代码:

import numpy as np
def gpt2(inputs, wte, wpe, blocks, ln_f, n_head):
pass # TODO: implement this
def generate(inputs, params, n_head, n_tokens_to_generate):
from tqdm import tqdm
for _ in tqdm(range(n_tokens_to_generate), “generating”): # auto-regressive decode loop
logits = gpt2(inputs, **params, n_head=n_head) # model forward pass
next_id = np.argmax(logits[-1]) # greedy sampling
inputs = np.append(inputs, [next_id]) # append prediction to input
return list(inputs[len(inputs) – n_tokens_to_generate :]) # only return generated ids
def main(prompt: str, n_tokens_to_generate: int = 40, model_size: str = “124M”, models_dir: str = “models”):
from utils import load_encoder_hparams_and_params
# load encoder, hparams, and params from the released open-ai gpt-2 files
encoder, hparams, params = load_encoder_hparams_and_params(model_size, models_dir)
# encode the input string using the BPE tokenizer
input_ids = encoder.encode(prompt)
# make sure we are not surpassing the max sequence length of our model
assert len(input_ids) + n_tokens_to_generate < hparams[“n_ctx”]
# generate output ids
output_ids = generate(input_ids, params, hparams[“n_head”], n_tokens_to_generate)
# decode the ids back into a string
output_text = encoder.decode(output_ids)
return output_text
if name == “__main__”:
import fire
fire.Fire(main)

然后从克隆存储库,安装依赖项等步骤一步步教你如何在电脑上运行GPT。

其中,还不乏一些贴心的小tips,比如说如果使用的是M1 Macbook,那在运行pip install之前,需要将requments.txt中的tensorflow更改为tensorflow-macos。

此外,对于代码的四个部分:gpt2,generate,main以及fire.Fire(main),博主也有做详细解释。

等到代码能够运行之后,下一步博主就准备详细介绍编码器、超参数(hparams)以及参数(params)这三部分了。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

直接在笔记本或者Python会话中运行下面这个代码:

from utils import load_encoder_hparams_and_params
encoder, hparams, params = load_encoder_hparams_and_params(“124M”, “models”)

Bingo!一些必要的模型和tokenizer文件就直接下载到model/124M,编码器、hparams和params也能直接加载。

更具体的内容这里就不多说了,教程的链接已经附在文末。

一些基础神经网络层的介绍

这一趴涉及到的知识就更加基础了,因为下一趴是实际GPT自身的架构,所以在此之前,需要了解一些非特定于GPT的更基本的神经网络层

博主介绍了GeLU、Softmax函数以及Layer Normalization和Linear。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

GPT架构

终于!这部分要来讲GPT自身的架构了,博主从transformer的架构引入。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

△transformer架构

GPT的架构只使用了transformer中的解码器堆栈(即图表的右边部分),并且其中的的“交叉注意”层也没有用到。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

△GPT架构

随后,博主将GPT的架构总结成了三大部分:

  • 文本 + 位置嵌入
  • 变压器解码器堆栈
  • 下一个token预测头

并且还将这三部分用代码展示了出来,是酱紫的:

def gpt2(inputs, wte, wpe, blocks, ln_f, n_head): # [n_seq] -> [n_seq, n_vocab]
# token + positional embeddings
x = wte[inputs] + wpe[range(len(inputs))] # [n_seq] -> [n_seq, n_embd]
# forward pass through n_layer transformer blocks
for block in blocks:
x = transformer_block(x, block, n_head=n_head) # [n_seq, n_embd] -> [n_seq, n_embd]
# projection to vocab
x = layer_norm(x, ln_f) # [n_seq, n_embd] -> [n_seq, n_embd]
return x @ wte.T # [n_seq, n_embd] -> [n_seq, n_vocab]

再后面,就是关于这三部分的更多细节……

测试构建的GPT

这部分将全部的代码组合在一起,就得到了gpt2.py,统共有120行代码,删除注释和空格的话,就是60行。

然后测试一下!

python gpt2.py \
“Alan Turing theorized that computers would one day become” \
–n_tokens_to_generate 8

结果是这样的:

the most powerful machines on the planet.

成功了!

一些后续补充

最后一部分,博主也总结了这短短60行代码的不足:非常低效!

不过他还是给出了两个可以让GPT变高效的方法:

  • 同时地而不是顺序地执行注意力计算。
  • 实现 KV 缓存。

此外,博主还推荐了一些训练模型、评估模型以及改进架构的方法和教程。

感兴趣的话,直接戳文末链接~

作者介绍

Jay Mody,目前在加拿大一家NLP初创公司Cohere从事机器学习的工作,此前,他还分别在特斯拉和亚马逊作为软件工程师实习过一段时间。

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

除了这篇教程之外,小哥的博客网站上还有更新其他文章,并且都有附代码~

60行代码就能构建GPT!网友:比之前的教程都要清晰|附代码

代码传送门:
https://github.com/jaymody/picoGPT/blob/29e78cc52b58ed2c1c483ffea2eb46ff6bdec785/gpt2_pico.py#L3-L58
教程链接:
https://jaykmody.com/blog/gpt-from-scratch/#putting-it-all-together