附上参考链接:Datawhale
简单介绍下背景知识
一.背景知识
1.大模型的定义:
为了对人类语言的内在规律进行建模,研究者们提出使用语言模型(language model)来准确预测词序列中 下一个词
或者 缺失的词
的概率。
目前已经有四代语言模型了
1) 统计语言模型(Statistical Language Model, SLM):
使用马尔可夫假设(Markov Assumption)来建模语言序列的 𝑛 元(𝑛-gram)语言模型
2) 神经语言模型(Neural Language Model, NLM):
基于神经网络构建语言模型,如循环神经网络(Recurrent Neural Networks, RNN),并学习上下文相关的词表示(即分布式词向量),也被称为词嵌入(Word Embedding)。代表性工作:word2vec
3) 预训练语言模型(Pre-trained Language Model, PLM):
使用大量的无标注数据预训练双向 LSTM(Bidirectional LSTM, biLSTM)或者Transformer,然后在下游任务上进行微调(Fine-Tuning)。代表性工作:ELMo、BERT、GPT-1/2
4) 大语言模型(Large Language Model, LLM):
基于“扩展法则”(Scaling Law),即通过增加模型参数或训练数据,可以提升下游任务的性能,同时具有小模型不具有的“涌现能力”(Emergent Abilities)。代表性工作:GPT-3、ChatGPT、Claude、Llama
2.如何构建大模型
通常来说,大模型的构建过程可以分为预训练(Pretraining)、有监督微调(Supervised Fine-tuning, SFT)、基于人类反馈的强化学习对齐(Reinforcement Learning from Human Feedback, RLHF)三个阶段。接下来,我们依次介绍一下这三个阶段:
1.预训练
预训练指使用海量的数据进行模型参数的初始学习,旨在为模型参数寻找到一个优质的“起点”。这一概念最初在计算机视觉领域萌芽,通过在ImageNet(一个大型图像数据集)上的训练,为模型参数奠定了坚实的基础。随后,这一理念被自然语言处理(NLP)领域采纳,word2vec开创先河,利用未标注文本构建词嵌入模型;ELMo、BERT及GPT-1则进一步推动了“预训练-微调”范式的普及。
2.有监督微调
经过大规模预训练后,模型已经具备较强的模型能力,能够编码丰富的世界知识,但是由于预训练任务形式所限,这些模型更擅长于文本补全,并不适合直接解决具体的任务。
尽管引入了诸如上下文学习(In-Context Learning, ICL)等提示学习策略以增强模型的适应性,但模型本身在下游任务解决上的能力仍受限。为了克服这一局限,预训练后的大型语言模型往往需经历微调过程,以提升其在特定任务中的表现。
3.基于人类反馈的强化学习对齐
除了提升任务的解决能力外,大语言模型与人类期望、需求及价值观的对齐(Alignment)至关重要,这对于大模型的应用具有重要的意义。
样例:
penAI在 2022 年初发布了 InstructGPT 论文,详尽阐述了如何实现语言模型与人类对齐。论文提出了基于人类反馈的强化学习对齐(Reinforcement Learning from Human Feedback, RLHF),通过指令微调后的强化学习,提升模型的人类对齐度。RLHF的核心在于构建一个反映人类价值观的奖励模型(Reward Model)。这一模型的训练依赖于专家对模型多种输出的偏好排序,通过偏好数据训练出的奖励模型能够有效评判模型输出的质量。
3.开源大模型和闭源大模型
-
一是选择将模型开源的组织,他们秉持着促进学术交流和技术创新的理念,让全球的研究者和开发者都能受益于这些模型。通过开放模型的代码和数据集,他们加速了整个AI社区的发展,促进了创新和技术的民主化。这一阵营的代表有Meta AI、浪潮信息等。
-
另一类则是保持模型闭源的公司,它们通常将模型作为核心竞争力,用于提供专有服务或产品,以维持商业优势。闭源模型通常伴随着专有技术和服务,企业可以通过API等方式提供给客户使用,而不直接公开模型的细节或代码。这种模式有助于保障公司的商业利益,同时也为用户提供了稳定和安全的服务。这一阵营的代表有OpenAI、百度等。
4.大模型时代挖掘模型能力的开发范式
-
有手就行的Prompt工程
-
Embedding辅助给LLM外接大脑
-
参数高效微调
5.大模型应用开发 必知必会
通常,一个完整的大模型应用包含一个客户端和一个服务端。
客户端接收到用户请求后,将请求输入到服务端,服务端经过计算得到输出后,返回给客户端回复用户的请求。
二.解释baseline
# 导入所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
import streamlit as st
# 创建一个标题和一个副标题
st.title("💬 Yuan2.0 智能编程助手")
# 源大模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('IEITYuan/Yuan2-2B-Mars-hf', cache_dir='./')
# model_dir = snapshot_download('IEITYuan/Yuan2-2B-July-hf', cache_dir='./')
# 定义模型路径
path = './IEITYuan/Yuan2-2B-Mars-hf'
# path = './IEITYuan/Yuan2-2B-July-hf'
# 定义模型数据类型
torch_dtype = torch.bfloat16 # A10
# torch_dtype = torch.float16 # P100
# 定义一个函数,用于获取模型和tokenizer
@st.cache_resource
def get_model():
print("Creat tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(path, add_eos_token=False, add_bos_token=False, eos_token='<eod>')
tokenizer.add_tokens(['<sep>', '<pad>', '<mask>', '<predict>', '<FIM_SUFFIX>', '<FIM_PREFIX>', '<FIM_MIDDLE>','<commit_before>','<commit_msg>','<commit_after>','<jupyter_start>','<jupyter_text>','<jupyter_code>','<jupyter_output>','<empty_output>'], special_tokens=True)
print("Creat model...")
model = AutoModelForCausalLM.from_pretrained(path, torch_dtype=torch_dtype, trust_remote_code=True).cuda()
print("Done.")
return tokenizer, model
# 加载model和tokenizer
tokenizer, model = get_model()
# 初次运行时,session_state中没有"messages",需要创建一个空列表
if "messages" not in st.session_state:
st.session_state["messages"] = []
# 每次对话时,都需要遍历session_state中的所有消息,并显示在聊天界面上
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
# 如果用户在聊天输入框中输入了内容,则执行以下操作
if prompt := st.chat_input():
# 将用户的输入添加到session_state中的messages列表中
st.session_state.messages.append({"role": "user", "content": prompt})
# 在聊天界面上显示用户的输入
st.chat_message("user").write(prompt)
# 调用模型
prompt = "<n>".join(msg["content"] for msg in st.session_state.messages) + "<sep>" # 拼接对话历史
inputs = tokenizer(prompt, return_tensors="pt")["input_ids"].cuda()
outputs = model.generate(inputs, do_sample=False, max_length=1024) # 设置解码方式和最大生成长度
output = tokenizer.decode(outputs[0])
response = output.split("<sep>")[-1].replace("<eod>", '')
# 将模型的输出添加到session_state中的messages列表中
st.session_state.messages.append({"role": "assistant", "content": response})
# 在聊天界面上显示模型的输出
st.chat_message("assistant").write(response)
-
导入库:
导入所需要的依赖,包括
transformers
,torch
和streamlit
。其中torch
魔搭本身已经安装,transformers
和streamlit
在第二步也安装完毕。 -
模型下载:
Yuan2-2B-Mars支持通过多个平台进行下载,包括魔搭、HuggingFace、OpenXlab、百度网盘、WiseModel等。因为我们的机器就在魔搭,所以这里我们直接选择通过魔搭进行下载。模型在魔搭平台的地址为 IEITYuan/Yuan2-2B-Mars-hf。
模型下载使用的是 modelscope 中的 snapshot_download 函数,第一个参数为模型名称 IEITYuan/Yuan2-2B-Mars-hf
,第二个参数 cache_dir
为模型保存路径,这里.
表示当前路径。
模型大小约为4.1G,由于是从魔搭直接进行下载,速度会非常快。下载完成后,会在当前目录增加一个名为 IEITYuan
的文件夹,其中 Yuan2-2B-Mars-hf
里面保存着我们下载好的源大模型。
3.模型加载:
使用 transformers
中的 from_pretrained
函数来加载下载好的模型和tokenizer,并通过 .cuda()
将模型放置在GPU上。另外,这里额外使用了 streamlit
提供的一个装饰器 @st.cache_resource
,它可以用于缓存加载好的模型和tokenizer。
4.读取用户输入:
使用 streamlit
提供的 chat_input()
来获取用户输入,同时将其保存到对话历史里,并通过st.chat_message("user").write(prompt)
在聊天界面上进行显示。
5.对话历史拼接:
对于 Yuan2-2B-Mars
模型来说,输入需要在末尾添加 <sep>
,模型输出到 <eod>
结束。如果输入是多轮对话历史,需要使用 <n>
进行拼接,并且在末尾添加 <sep>
。
6.模型调用:
输入的prompt需要先经tokenizer切分成token,并转成对应的id,并通过 .cuda()
将输入也放置在GPU上。然后调用 model.generate()
生成输出的id,并通过 tokenizer.decode()
将id转成对应的字符串。最后从字符串中提取模型生成的内容(即 <sep>
之后的字符串),并删除末尾的 <eod>
,得到最终的回复内容。
7.显示模型输出:
得到回复内容后,将其保存到对话历史里,并通过st.chat_message("assistant").write(response)
在聊天界面上进行显示。
效果图: