自己做了一个小GPT——eggGPT
为了正式实践一下Transformer,我自己训练了一个GPT。
为什么做这件事
一直听闻Transformer架构的顶顶大名,但是始终没有实践过,于是打算自己做一个GPT试试看。
由于我的显卡只是一个8G显存的4060 Laptop,而且剩下的硬盘空间也很小了。所以我只想做一个能回我话的超级超级超级小模型,我并没有抱着模型能多牛逼的想法。不过我得自己推一遍Transformer中的注意力计算公式,代码中模型架构的部分也得自己手搓一遍,不用封装好的Transformer。然后在自己电脑上把预训练和微调都跑完。
预训练进化史:从v1-7.3M到v5-110M
现在勉强可以回复的eggGPT-v5-chat-110M经过了好几轮迭代。
预训练模型 v1-7.3M
这是我训练的第一个预训练模型,模型配置如下
| 词表大小 | 8000 |
| 最大序列长度 | 256 |
| TransformerBlock 数量 | 4 |
| 注意力头数量 | 4 |
| embedding 维度 | 256 |
| 参数量 | 7,321,088 |
它的训练语料是我自己准备的,有我的博客文章,我在学校写的一些报告,我的游戏构思以及基本zlib上下载的小说和世界名著。训练集 27,837,231 tokens,验证集 1,461,548 tokens。说实话它的生成效果非常差,由于我给了好多小说资料,因此它生成的文本内容绝大多数都是对话,并且主题漂移严重,很容易退化成复读循环。
预训练模型 v2-16.9M
于是在训练第二版预训练模型时候,我决定扩大模型参数,让模型强大一些。
| 词表大小 | 8000 |
| 最大序列长度 | 256 |
| TransformerBlock 数量 | 6 |
| 注意力头数量 | 6 |
| embedding 维度 | 384 |
| 参数量 | 16,889,856 |
此外,我还优化了一下训练语料,减少了小说的占比,加入了一些纪实作品。训练集 39,194,453 tokens,验证集 2,059,284 tokens。从最后测试结果上来看,15 样本中 15 个退化为复读循环。典型复读内容:「爱您」「先生」「巴黎」「她」。词汇更多样(能冒出《1Q84》的”天吾”、古典小说腔),但连贯性与 v1 相比没有实质提升。
预训练模型 v3-33M
自己准备语料还是太累了,而且我准备的语料质量似乎并不是那么好。因此对于第三个版本的预训练模型我打算使用公开的语料进行训练并且进一步扩大模型参数。
| 词表大小 | 8000 |
| 最大序列长度 | 256 |
| TransformerBlock 数量 | 8 |
| 注意力头数量 | 8 |
| embedding 维度 | 512 |
| 参数量 | 33,543,168 |
这次我不再自己准备训练语料,而是使用中文维基语料。抽样 10.4 万篇 → 清洗后 train 124,599,922 tokens + val 6,538,580 tokens。虽然训练语料大大增加,但是我发现预训练出来的结果依旧不尽人意。由于全是维基语料,虽然没有了对话模式,但是百科腔非常严重。并且由于语料数据没有清洗干净,导致会生成一些网页的标签特征。
预训练模型 v4-42M
对于第四版预训练模型,我又优化了一下模型架构,8000的词汇量还是太少了,于是扩大了整整一倍。另外增加了最长序列长度,试图强化一下上下文记忆。
| 词表大小 | 16000 |
| 最大序列长度 | 384 |
| TransformerBlock 数量 | 8 |
| 注意力头数量 | 8 |
| embedding 维度 | 512 |
| 参数量 | 41,800,704 |
这一次我将维基中文语料又进行了一次数据清理,去掉了网页结构样板,留下更加干净一些的文本。然后我基于干净的语料重新训练了tokenizer。最后得到train 74,271,637 tokens + val 3,908,302 tokens。从训练结果上来看,要更好一些,至少没有了v3版本的网页注脚等标签数据。但是由于是维基百科,所以模型训练出来的仍然是说明文,缺少话题和故事的延续性。看来纯维基百科数据并不合适。
预训练模型 v4.1-42M
因此,对于下一个版本,我需要优化一下训练语料。保持模型架构不动,打算在维基的基础上,加入一些话题和故事的叙述性文本。
将60%小说和40%维基混合,得到train 61.66M tokens / val 3.49M tokens的训练数据。从训练结果上来看,v4.1能够进入叙事,有角色、对话以及场景推进,但是仍然存在翻译腔语法、逻辑混乱的问题。着大抵是数据集上的瓶颈了。
诗歌模型 poem-4.1M
在进行预训练之余,我突发奇想。反正我现在训练出来的小的预训练模型都是讲得前言不搭后语的,而人类的诗歌在我这种外行看来正好也是前言不搭后语的,要是我用一些诗歌数据集训练一个诗歌模型效果如何。由于我的zlib一天只能下10本书,所以我准备的诗歌数据集(海子、徐志摩、艾青、郭沫若、但丁、莎士比亚等等的作品)并不是很多,因此诗歌模型并不会设计得太大。
| 词表大小 | 12000 |
| 最大序列长度 | 256 |
| TransformerBlock 数量 | 4 |
| 注意力头数量 | 4 |
| embedding 维度 | 192 |
| 参数量 | 4,132,992 |
最终训练出来的生成表现真是太差了,产出完全就是虚词汤。模型只是学会了中文的样子,数据集太少不够学任何文学意义上的搭配结构。下面是它写的一首诗,估计高人都无法解读。
面朝大海,春暖花开下
一
在水;
而用山。
我就一个又对从那在白
有不人
我,这小。
把花,如被血上
…
预训练模型 v5-110M
最终,我干脆整个大的。换个大一些的模型,换个好些的训练语料。
| 词表大小 | 32000 |
| 最大序列长度 | 512 |
| TransformerBlock 数量 | 12 |
| 注意力头数量 | 12 |
| embedding 维度 | 768 |
| 参数量 | 110,025,216 |
模型参数量来到了一个亿!训练数据使用开源的Skypile-150B(但是只取其中的一部分),然后加上一些古典名著以及少量的维基文库书籍。得到train 323,082,851 tokens / val 16,977,951 tokens的训练数据。在经过14.77小时的训练后,终于得到了一个还算说的过去的预训练基座模型。虽然仍有不足之处,但是续写水平以及逻辑水平已经达到了小学生的能力。以下是它的一个续写片段。
开头:
有一天,小明在上学的路上...
预训练模型 v5-110M 续写:
有一天,小明在上学的路上遇见了“大爷”。
这天早晨,小明在街上遇到“大爷”孙悟空。他一抬头就看见唐僧正骑着马从背后赶去,但是他还是不以为然地站了起来,小明就对他说:“你是大爷。”小明很感激孙悟空,于是便把马拿在身上放了回去。
让它开口说话:指令微调
预训练出来的 v5 只会续写,给它一句话,它接着写,但不会回答问题。要让它变成能对话的模型,需要指令微调(SFT):
- 数据用 Belle 的中文指令数据集(0.5M 条”指令-回答”对),加上我自己写的几十条身份样本
- 训练用掩码交叉熵——loss 只算助手回答那一部分,避免模型自己当起来了用户
经过4小时的微调训练,得到了 v5-chat。现在才有了勉强聊天的能力。实际上,以聊天机器人的能力来看它的话,它完全是不合格的,相当呆。连最简单的1+1都不会。不过受限于模型参数、训练语料、硬件成本、时间成本,能达到这种程度我已经很满意了。毕竟着只是一个我用来学习的练手项目。
试一试
下面是 eggGPT 的体验区。如果后端在线的话,可以试着跟它聊聊。