本地部署大模型涉及三个方面需求:训练、微调和推理,其中:
训练:算力最密集,消耗的算力通常是推理过程的至少三个数量级以上;
微调:算力需求低于训练,但高于推理;
推理:算力消耗最低;
绝大多数人的关注点应集中在推理和微调的性能上;
PIP 和CONDA 都是包管理工具, 区别是CONDA会链式调用下载, 优先用CONDA下
先从GitHub中下载ChatGLM3-6B的一些运行文件和项目代码( 并不包含ChatGLM3-6B这个模型 )。
再从 Hugging Face 中Hugging Face 中下载ChatGLM3-6B的权重
Hugging Face是一个丰富的模型库,开发者可以上传和共享他们训练好的机器学习模型。这些模型通常是经过大量数据训练的,并且很大,相当于大模型界的GitHub.
启动模型前, 先用Conda创建一个虚拟环境, 这里采用Py版本3.11, 今后可以创建多个虚拟环境
模型就运行这个虚拟环境中
2. 交互
启动后, 可以直接在命令行进行交互, 但基于网页端的对话是目前非常通用的大语言交互方式
Gradio是一个Python库,用于快速创建用于演示机器学习模型的Web界面。开发者可以用几行代码为模型创建输入和输出接口,用户可以通过这些接口与模型进行交互。用户可以轻松地测试和使用机器学习模型,比如通过上传图片来测试图像识别模型,或者输入文本来测试自然语言处理模型。Gradio非常适合于快速原型设计和模型展示。
ChatGLM3官方项目组提供了两种Web端对话demo,两个示例应用功能一致,只是采用了不同的Web框架进行开发。
分别基于Gradio 和 Streamlit
3. 微调
全量微调对原始模型的所有参数全部做一个调整。但对于LLM,在消费级显卡上就做根本没有办法实现。
所以目前对于大模型来说,主流的微调技术叫做高效微调,这种方式是通过微调大模型少量或者额外的一些参数,固定预训练模型(LLM)参数,以此来降低计算和存储成本,同时,还可以在一定程度上实现与全量参数微调相当的性能。
本质上,现在的大模型要解决的问题,就是一个序列数据转换的问题:
输入序列 X = [x1, x2, …, xm], 输出序列Y = [y1, y2, …, yn],X和Y之间的关系是:Y = WX。
我们所说的“大模型”这个词:“大”是指用于训练模型的参数非常多,多达千亿、万亿;而“模型”指的就是上述公式中的矩阵W。
在这里,矩阵W就是通过机器学习,得出的用来将X序列,转换成Y序列的权重参数组成的矩阵。
需要特别说明:这里为了方便理解,做了大量的简化。在实际的模型中,会有多个用于不同目的的权重参数矩阵,也还有一些其它参数。
为什么要对大模型进行微调
通常,要对大模型进行微调,有以下一些原因:
第一个原因是,因为大模型的参数量非常大,训练成本非常高,每家公司都去从头训练一个自己的大模型,这个事情的性价比非常低;
第二个原因是,Prompt Engineering的方式是一种相对来说容易上手的使用大模型的方式,但是它的缺点也非常明显。因为通常大模型的实现原理,都会对输入序列的长度有限制,Prompt Engineering 的方式会把Prompt搞得很长。
越长的Prompt,大模型的推理成本越高,因为推理成本是跟Prompt长度的平方正向相关的。
另外,Prompt太长会因超过限制而被截断,进而导致大模型的输出质量打折口,这也是一个非常严重的问题。
对于个人使用者而言,如果是解决自己日常生活、工作中的一些问题,直接用Prompt Engineering的方式,通常问题不大。
但对于对外提供服务的企业来说,要想在自己的服务中接入大模型的能力,推理成本是不得不要考虑的一个因素,微调相对来说就是一个更优的方案。
第三个原因是,Prompt Engineering的效果达不到要求,企业又有比较好的自有数据,能够通过自有数据,更好的提升大模型在特定领域的能力。这时候微调就非常适用。
第四个原因是,要在个性化的服务中使用大模型的能力,这时候针对每个用户的数据,训练一个轻量级的微调模型,就是一个不错的方案。
第五个原因是,数据安全的问题。如果数据是不能传递给第三方大模型服务的,那么搭建自己的大模型就非常必要。通常这些开源的大模型都是需要用自有数据进行微调,才能够满足业务的需求,这时候也需要对大模型进行微调。
如何对大模型进行微调
从参数规模的角度,大模型的微调分成两条技术路线:
一条是对全量的参数,进行全量的训练,这条路径叫全量微调FFT(Full Fine Tuning)。
一条是只对部分的参数进行训练,这条路径叫PEFT(Parameter-Efficient Fine Tuning)。
FFT的原理,就是用特定的数据,对大模型进行训练,将W变成W,W
相比W ,最大的优点就是上述特定数据领域的表现会好很多。
但FFT也会带来一些问题,影响比较大的问题,主要有以下两个:
一个是训练的成本会比较高,因为微调的参数量跟预训练的是一样的多的;
一个是叫灾难性遗忘(Catastrophic Forgetting),用特定训练数据去微调可能会把这个领域的表现变好,但也可能会把原来表现好的别的领域的能力变差。
PEFT主要想解决的问题,就是FFT存在的上述两个问题,PEFT也是目前比较主流的微调方案。
从训练数据的来源、以及训练的方法的角度,大模型的微调有以下几条技术路线:
一个是监督式微调SFT(Supervised Fine Tuning),这个方案主要是用人工标注的数据,用传统机器学习中监督学习的方法,对大模型进行微调;
一个是基于人类反馈的强化学习微调RLHF(Reinforcement Learning with Human Feedback),这个方案的主要特点是把人类的反馈,通过强化学习的方式,引入到对大模型的微调中去,让大模型生成的结果,更加符合人类的一些期望;
还有一个是基于AI反馈的强化学习微调RLAIF(Reinforcement Learning with AI Feedback),这个原理大致跟RLHF类似,但是反馈的来源是AI。这里是想解决反馈系统的效率问题,因为收集人类反馈,相对来说成本会比较高、效率比较低。
不同的分类角度,只是侧重点不一样,对同一个大模型的微调,也不局限于某一个方案,可以多个方案一起。
微调的最终目的,是能够在可控成本的前提下,尽可能地提升大模型在特定领域的能力。
3.1 主流的高效微调方法介绍
3.1.1 Freeze
Freeze方法指的是参数冻结,对原始模型的大部分参数进行冻结,仅训练少部分的参数,这样就可以大大减少显存的占用,从而完成对大模型的微调。
特别是在Bert模型出来的时候,比较会常用到Freeze的这样一个微调方法,比如Bert有12层,我们把前10层冻结了,只训练后两层。这是一种比较简单微调方法,由于冻结的参数是大部分,微调的参数是少部分,因此在代码中只需要设置需要微调的层的参数即可,把不需要参加训练的层数 requires_grad 设置为False,不让其进行更新,从而达到冻结的这样一个效果。
3.1.2 Prefix-Tuning
首先了解两个概念
- Hard Prompt:也称离散Prompt,是一个实际的文本字符串(自然语言,人工可读),通常由中文或英文词汇组成;
- Soft Prompt:也称连续Prompt,通常是在向量空间优化出来的提示,通过梯度搜索之类的方式进行优化;
在Hart Promot中,提示语的变化对模型最终的性能特别敏感,加一个词、少一个词或者变动位置都会造成比较大的变化。成本比较高,并且效果不太好。
传统的微调范式Fine-turning会利用预训练模型去对不同的下游任务进行微调,对每个任务都要保存一份微调后的模型权重。比如下图展示的三个不同任务的Transformer模型,分别用来做翻译、摘要和将格式转化(table-to-text)。
每个任务都有自己的微调模型,这意味着模型的所有权重都在微调过程中针对特定任务进行了更新。这种方法通常需要大量的数据和计算资源,因为整个模型都在学习任务特定的知识。
Prefix Tuning提出固定预训练LM,为LM添加可训练,任务特定的向量(virtual tokens)作为前缀,这样就可以为不同任务保存不同的前缀,微调成本也小;
对于同时具有 Encoder 和 Decoder 的模型来说, 两者都要加上prefix
Encoder端增加前缀是为了引导输入部分的编码,Decoder端增加前缀是为了引导后续token的生成
对于自回归架构模型:在句子前面添加前缀,得到 z = [PREFIX; x; y],合适的上文能够在固定 LM 的情况下去引导生成下文(比如:GPT3的上下文学习)。
该方法其实和构造Prompt类似,只是Prompt是人为构造的“显式”的提示,并且无法更新参数,而Prefix则是可以学习的“隐式”的提示。
3.1.3 P-Tunning V2
可以简单地认为 P-Tunning V2 是 Prefix-Tuning 的改进
P-Tuning v2在每一层都加入了Prompts tokens作为输入,而不是仅仅加在输入层,这带来两个方面的好处:
- 更多可学习的参数(从P-tuning和Prompt Tuning的0.01%增加到0.1%-3%),同时也足够参数高效。
- 加入到更深层结构中的Prompt能给模型预测带来更直接的影响。
ChatGLM3 - 6B 就是采用 P-Tuning v2 进行高效微调
提前配置使用INT4 进行微调, 默认FB16, 需要4090级别的显卡
3.1.4 LoRA
LoRA是跟Prompt Tuning和Prefix Tuning完全不相同的另一条技术路线。
LoRA背后有一个假设:我们现在看到的这些大语言模型,它们都是被过度参数化的。而过度参数化的大模型背后,都有一个低维的本质模型。
通俗讲人话:大模型参数很多,但并不是所有的参数都是发挥同样作用的;大模型中有其中一部分参数,是非常重要的,是影响大模型生成结果的关键参数,这部分关键参数就是上面提到的低维的本质模型。
LoRA的基本思路,包括以下几步:
首先, 要适配特定的下游任务,要训练一个特定的模型,将Y=WX变成Y=(W+∆W)X,这里面∆W主是我们要微调得到的结果;
其次,将∆W进行低维分解∆W=AB (∆W为m * n维,A为m * r维,B为r * n维,r就是上述假设中的低维);
接下来,用特定的训练数据,训练出A和B即可得到∆W,在推理的过程中直接将∆W加到W上去,再没有额外的成本。
另外,如果要用LoRA适配不同的场景,切换也非常方便,做简单的矩阵加法即可:(W + ∆W) - ∆W + ∆W`。
3.1.5 QLoRA
即量化LoRA,可以有效降低内存占用.