一、创建大模型应用
流程:开通阿里云PAI-DSW——>在魔搭社区创建PAI实例——>Demo搭建——>启动并体验
疑点:PAI- DSW?GIT?git lfs install
?streamlit
?
Demo搭建:
# 文件下载
git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/AICamp_yuan_baseline.git
# 环境安装
pip install streamlit==1.24.0
# 启动
streamlit run AICamp_yuan_baseline/Task\ 1:零基础玩转源大模型/web_demo_2b.py --server.address 127.0.0.1 --server.port 6006
二、大模型开发必知必会
1. 背景知识
四代语言模型:
统计语言模型;神经语言模型;预训练语言模型;大语言模型
大模型构架:预训练,有监督微调,基于人类反馈的强化学习对齐
2. 挖掘模型能力
Prompt工程:上下文学习,思维链提示
Embedding辅助给LLM外接大脑:(解决)知识局限性、数据安全性、大数据幻觉
参数高效微调:
3
三、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='./')
# 定义模型路径
path = './IEITYuan/Yuan2-2B-Mars-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
中的from_pretrained
函数来加载下载好的模型和tokenizer,并通过.cuda()
将模型放置在GPU上。另外,这里额外使用了 streamlit
提供的一个装饰器 @st.cache_resource
,它可以用于缓存加载好的模型和tokenizer。
读取用户输入
使用 streamlit
提供的 chat_input()
来获取用户输入,同时将其保存到对话历史里,并通过st.chat_message("user").write(prompt)
在聊天界面上进行显示。
对话历史拼接
对于Yuan2-2B-Mars
模型来说,输入需要在末尾添加<sep>
,模型输出到 <eod>
结束。如果输入是多轮对话历史,需要使用 <n>
进行拼接,并且在末尾添加 <sep>
。
模型调用
输入的prompt需要先经tokenizer切分成token,并转成对应的id,并通过.cuda()
将输入也放置在GPU上。然后调用model.generate()
生成输出的id,并通过tokenizer.decode()
将id转成对应的字符串。最后从字符串中提取模型生成的内容(即<sep>
之后的字符串),并删除末尾的<eod>
,得到最终的回复内容。
显示模型输出
得到回复内容后,将其保存到对话历史里,并通过st.chat_message("assistant").write(response)
在聊天界面上进行显示。
其它细节
定义模型数据类型
-
torch.bfloat16
:bfloat16
是一种16位的浮点数表示方法,它使用了8位来表示指数,7位来表示尾数(mantissa),还有1位用于符号。- 这种数据类型特别适用于需要高计算性能但对精度要求不是极致的场景,比如在大规模机器学习训练和推理中。
A10
可能指的是某种特定的硬件或计算环境,它可能优化了bfloat16
的使用。
-
torch.float16
:float16
也是16位的浮点数,但它使用了5位来表示指数,10位来表示尾数,还有1位用于符号。- 相比于
bfloat16
,float16
提供了更高的精度,但可能在计算性能上有所牺牲。 P100
可能指的是NVIDIA的一款GPU,它支持float16
计算,并且在某些情况下可以通过使用float16
来加速计算。
get_model()
这段代码定义了一个名为 get_model
的函数,它的主要作用是加载并准备一个因果语言模型(Causal Language Model,简称 CLM)以及与之对应的分词器(Tokenizer)。下面是对这段代码的详细讲解:
- 打印提示信息:
- 函数开始时,首先打印 “Creat tokenizer…”,提示用户正在创建分词器。
- 创建分词器:
- 使用
AutoTokenizer.from_pretrained
方法从指定的path
加载预训练的分词器。 add_eos_token=False
和add_bos_token=False
参数表示不添加结束符(End Of Sentence)和开始符(Begin Of Sentence)到分词器中。eos_token='<eod>'
参数指定了一个自定义的结束符标记。- 接着,使用
add_tokens
方法向分词器中添加一系列特殊的标记(tokens),这些标记在后续的文本处理中可能会有特殊用途。
- 使用
- 打印提示信息:
- 打印 “Creat model…”,提示用户正在创建模型。
- 创建模型:
- 使用
AutoModelForCausalLM.from_pretrained
方法从相同的path
加载预训练的因果语言模型。 torch_dtype=torch_dtype
参数指定了模型使用的数据类型,这可以是torch.bfloat16
、torch.float16
或其他 PyTorch 支持的数据类型。trust_remote_code=True
参数表示信任远程代码,这在加载模型时可能会涉及到执行一些远程脚本或代码。.cuda()
方法将模型移动到 GPU 上,以便进行加速计算。
- 使用
- 打印完成提示:
- 打印 “Done.”,表示模型和分词器已经成功加载并准备好。
if prompt := st.chat_input():
prompt := st.chat_input()
:这里使用了海象运算符来同时调用st.chat_input()
函数并将其返回值赋给变量prompt
。但是,st.chat_input()
通常不会在每次代码执行时都返回一个值;它更可能是在等待用户输入时阻塞执行,或者仅在用户实际输入并发送消息时返回一个值。因此,这种用法在标准的Streamlit应用中可能不是直接可行的,除非Streamlit的某个特定版本或插件修改了st.chat_input()
的行为。if prompt:
:这部分代码检查prompt
变量是否有值(即用户是否输入了内容)。在Python中,空字符串、None
、0
、False
、空列表等都被视为布尔值False
,而任何其他值都被视为True
。因此,如果用户输入了非空字符串,则条件为真,执行if
语句块内的代码。
“< n>”.join()
join()
是Python字符串的一个方法,它接受一个可迭代对象(如列表、元组、集合等),并将该可迭代对象中的所有元素连接成一个新的字符串。- 在这个方法中,
"<n>"
是连接符(分隔符),意味着每个消息内容之间将用这个字符串来分隔。 - 因此,如果
st.session_state.messages
包含三个消息,其"content"
分别为"Hello"
,"World"
, 和"!"
,那么"<n>".join(msg["content"] for msg in st.session_state.messages)
的结果将是"Hello<n>World<n>!"
。
inputs
tokenizer(prompt, return_tensors="pt")
:这里,tokenizer
是一个用于将文本(prompt
)转换为模型可以理解的数值表示(通常是token IDs)的对象。return_tensors="pt"
参数指定了返回的tensor类型,这里是PyTorch的tensor(pt
是PyTorch的缩写)。这意味着tokenizer
的输出将是一个PyTorch tensor,而不是其他类型的数组或列表。["input_ids"]
:tokenizer
的输出通常是一个包含多个键的字典,其中"input_ids"
键对应的值是一个tensor,包含了文本中每个token的ID。通过索引["input_ids"]
,我们从这个字典中提取出这个tensor。- PS: 当文本通过tokenizer进行转换时,它会被映射到一个由这些索引(即token IDs)组成的序列中。这个序列就是模型能够理解和处理的数值表示形式。因此,"input_ids"就是指向这个序列的引用,它包含了输入文本转换后得到的所有token的索引(IDs)。
.cuda()
:这是PyTorch中的一个方法,用于将tensor移动到GPU上(如果可用)。这可以加速计算,特别是在处理大型模型或数据集时。注意,如果当前环境中没有可用的CUDA设备(即没有NVIDIA GPU或CUDA未正确安装),调用.cuda()
将会失败。
output
do_sample
: 这是一个布尔值(True或False),用于控制生成过程中的采样策略。当do_sample=False
时,生成过程会采用贪心搜索(greedy search)策略,即在每个时间步选择概率最高的下一个token。这通常会导致生成结果更加保守和可预测,但也可能缺乏多样性。相比之下,当do_sample=True
时,会采用采样方法(如核心采样、top-k采样或top-p采样),这会增加生成文本的多样性,但也可能导致生成不如贪心搜索那么连贯或准确。output[0]:
由outputs
是一个列表(或类似的数据结构),outputs[0]
表示取这个列表中的第一个元素。在文本生成的上下文中,这通常意味着我们只对生成的第一个文本序列感兴趣(尽管在某些情况下,如批处理或生成多个候选文本时,outputs
可能包含多个序列)。tokenizer.decode(...):
:是tokenizer对象的一个方法,用于将数值表示(token IDs)转换回文本形式。这个方法接受一个包含token IDs的序列(如一个列表或tensor),并返回一个字符串,该字符串是这些token IDs对应的文本表示。
response
在自然语言处理(NLP)或文本处理的上下文中,这行代码通常用于处理由模型生成的文本或经过某种特定格式处理的文本字符串。这里,output
是一个字符串变量,它包含了可能由多个部分组成的文本,这些部分通过特定的分隔符(如 "<sep>"
)分隔开。这行代码的目的是从 output
字符串中提取出最后一个由 "<sep>"
分隔的部分,并移除该部分末尾可能存在的 "<eod>"
标记(如果存在的话)。
【PS】第一个“选取”是选择模型生成的第一个(或唯一一个)序列的token IDs进行解码。
【PS】第二个“选取”是在解码后的文本中,通过切割和索引操作来提取最后一个由"<sep>"
分隔的文本片段,并去除其中的"<eod>"
标记。
去官网查
- session_state
- chat_message