Datawhale AI夏令营第四期,大模型应用开发方向的Task1做完啦,回顾一下
tip : 大模型应用开发方向学习攻略及个人学习计划
个人学习计划 | |||
序号 | 日期 | 今日目标 | 今日学习资料 & 学习计划 |
Day1 | 8月9日 | 跑通Baseline并学习大模型开发基础 | |
Day2 | 8月10日 | 继续学习大模型开发基础并完成认证 | 大模型开发工程师考试入口(电脑端打开)https://linklearner.com/activity/quiz/14/11 |
Day3 | 8月12日 | 完成组队 | |
Day4 | 8月13日 | 线上头脑风暴 | |
Day5 | 8月14日 | RAG技术原理与实践 | |
Day6 | 8月15日 | 案例分析 | |
Day7 | 8月18日 | 模型微调技术原理与实践 | |
··· | ··· | ··· | ··· |
task 1
step 0 : Task 1 学习规划
step 1 : 开通阿里云PAI-DSW试用
步骤如下:
1 . 点击上方链接
2 . 登录
3 . 按下述 ① ② 步骤操作
4 . 阿里云 PAI-DSW 免费试用开通完成
step 2 : 在魔搭社区进行授权
链接:https://www.modelscope.cn/my/mynotebook/authorization
新用户需要先注册 & 绑定阿里云账号(可能伴有阿里云账号的实名认证)
新用户需要先注册 & 绑定阿里云账号的步骤如下:
在魔塔社区进行授权步骤如下:
( 如果这一步授权失败,可跳过此步骤,继续往下进行)
step 3 : 在魔搭社区创建PAI实例
链接:https://www.modelscope.cn/my/mynotebook/authorization
步骤如下:
点击终端
到这里就可以进行 step 4 了,如果 在魔搭无法授权 或 点击【打开】无法打开,可到阿里云控制台创建 & 打开实例 ,步骤如下:
1 . 打开阿里云控制台
2 . 创建实例
3 . 打开实例
此处的【新建实例】与魔搭的【创建实例】效果相同,如果魔搭处无法执行,也可以从此处创建
到这个界面即为打开实例成功
如果之前试用的额度已经过期,可使用魔搭的免费Notebook实例
步骤如下:
step 4 : 搭建 demo
1 . 文件下载
在终端中输入如下命令,回车运行
git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/AICamp_yuan_baseline.git
步骤如下:
1 . 打开终端
2 . 下载完成后,刷新即可
2 . 环境安装
在终端中输入如下命令,回车运行
pip install streamlit==1.24.0
3 . 启动 demo
在终端中输入如下命令,回车运行
streamlit run AICamp_yuan_baseline/Task\ 1:零基础玩转源大模型/web_demo_2b.py --server.address 127.0.0.1 --server.port 6006
step 5 : 对话体验
点击链接,跳转到浏览器新页面
显示Demo名称,后台正在运行中
等待后台完成模型下载和加载!
弹出对话框,可以开始对话啦
下面是与编程助手的对话
step 6 : 关闭PAI实例
链接:https://www.modelscope.cn/my/mynotebook/authorization
task 1 背景知识
1 . 什么是大模型
为了对人类语言的内在规律进行建模,研究者们提出使用语言模型(language model)来准确预测词序列中 下一个词
或者 缺失的词
的概率。
如下图所示,截止到目前,语言模型已经演化了四代,分别是:
-
统计语言模型(Statistical Language Model, SLM):使用马尔可夫假设(Markov Assumption)来建模语言序列的 𝑛 元(𝑛-gram)语言模型
-
神经语言模型(Neural Language Model, NLM):基于神经网络构建语言模型,如循环神经网络(Recurrent Neural Networks, RNN),并学习上下文相关的词表示(即分布式词向量),也被称为词嵌入(Word Embedding)。代表性工作:word2vec
-
预训练语言模型(Pre-trained Language Model, PLM):使用大量的无标注数据预训练双向 LSTM(Bidirectional LSTM, biLSTM)或者Transformer,然后在下游任务上进行微调(Fine-Tuning)。代表性工作:ELMo、BERT、GPT-1/2
-
大语言模型(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则进一步推动了“预训练-微调”范式的普及。
起初,预训练技术专注于解决特定类别的下游任务,例如文本分类、序列标注、序列到序列生成等传统NLP任务。OpenAI在GPT-2的研究中,提出了一种创新思路——通过大规模文本数据预训练,打造能够应对广泛任务的通用解决方案,并在GPT-3中将这一理念扩展至前所未有的超大规模。
在BERT等早期预训练模型中,模型架构和训练任务呈现出多样化特征。然而,随着GPT系列模型的兴起,“解码器架构+预测下一个词”的策略证明了其卓越效能,成为了当前主流的大模型技术路线。
在预训练过程中,首要任务是搜集和清洗海量的文本数据,确保剔除潜在的有害内容。鉴于模型的知识库几乎完全源自训练数据,数据的质量与多样性对模型性能至关重要。因此,获取高质、多元的数据集,并对其进行严谨的预处理,是打造高性能语言模型的关键步骤。
当前,多数开源模型的预训练均基于数T的token。例如,Llama-1、Llama-2、Llama-3的预训练规模分别为1T、2T和15T。除了对数据量的苛刻要求,预训练阶段对计算资源的需求也极为庞大。以Llama-1的65B参数模型为例,其在2,048块A100 80GB GPU集群上进行了接近三周的训练。此外,预训练过程中还涉及诸多细节,诸如数据配比、学习率调度、模型行为监测等,这些往往缺乏公开的最佳实践指导,需要研发团队具备深厚的训练经验与故障排查能力,以规避训练过程中的回溯与重复迭代,节约计算资源,提高训练效率。
总体而言,预训练不仅是一项技术挑战,更是一场对数据质量、算力投入与研发智慧的综合考验。
2 . 有监督微调
经过大规模预训练后,模型已经具备较强的模型能力,能够编码丰富的世界知识,但是由于预训练任务形式所限,这些模型更擅长于文本补全,并不适合直接解决具体的任务。
尽管引入了诸如上下文学习(In-Context Learning, ICL)等提示学习策略以增强模型的适应性,但模型本身在下游任务解决上的能力仍受限。为了克服这一局限,预训练后的大型语言模型往往需经历微调过程,以提升其在特定任务中的表现。
目前来说,比较广泛使用的微调技术是“有监督微调”(也叫做指令微调,Instruction Tuning),该方法利用成对的任务输入与预期输出数据,训练模型学会以问答的形式解答问题,从而解锁其任务解决潜能。经过指令微调后,大语言模型能够展现出较强的指令遵循能力,可以通过零样本学习的方式解决多种下游任务。
然而,值得注意的是,指令微调并非无中生有地传授新知,而是更多地扮演着催化剂的角色,激活模型内在的潜在能力,而非单纯地灌输信息。
相较于预训练所需的海量数据,指令微调所需数据量显著减少,从几十万到上百万条不等的数据,均可有效激发模型的通用任务解决能力,甚至有研究表明,少量高质量的指令数据(数千至数万条)亦能实现令人满意的微调效果。这不仅降低了对计算资源的依赖,也提升了微调的灵活性与效率。
3 . 基于人类反馈的强化学习对齐
除了提升任务的解决能力外,大语言模型与人类期望、需求及价值观的对齐(Alignment)至关重要,这对于大模型的应用具有重要的意义。
OpenAI在 2022 年初发布了 InstructGPT 论文,详尽阐述了如何实现语言模型与人类对齐。论文提出了基于人类反馈的强化学习对齐(Reinforcement Learning from Human Feedback, RLHF),通过指令微调后的强化学习,提升模型的人类对齐度。RLHF的核心在于构建一个反映人类价值观的奖励模型(Reward Model)。这一模型的训练依赖于专家对模型多种输出的偏好排序,通过偏好数据训练出的奖励模型能够有效评判模型输出的质量。
目前还有很多工作试图去除复杂的强化学习算法,或其他使用 SFT 方式来达到与 RLHF 相似的效果,从而简化模型的对齐过程。例如,直接偏好优化(Direct Preference Optimization, DPO),它通过与有监督微调相似的复杂度实现模型与人类对齐,是一种典型的不需要强化学习的对齐算法。相比RLHF,DPO不再需要在训练过程中针对大模型进行采样,同时超参数的选择更加容易。
最后,我们以开源大模型Llama-2-Chat为例,简要介绍一下其训练过程。
整个过程起始于利用公开数据进行预训练,获得Llama-2。在此之后,通过有监督微调创建了Llama-2-Chat的初始版本。随后,使用基于人类反馈的强化学习(RLHF)方法来迭代地改进模型,具体包括拒绝采样(Rejection Sampling)和近端策略优化(Proximal Policy Optimization, PPO)。在RLHF阶段,人类偏好数据也在并行迭代,以保持奖励模型的更新。
3 . 开源大模型和闭源大模型
根据上面的学习,我们不难发现,构建大模型不仅需要海量的数据,更依赖于强大的计算能力,以确保模型能够快速迭代和优化,从而达到预期的性能水平。鉴于此,全球范围内能够独立承担起如此庞大计算成本的机构屈指可数。这些机构可以分为以下两大阵营:
-
一是选择将模型开源的组织,他们秉持着促进学术交流和技术创新的理念,让全球的研究者和开发者都能受益于这些模型。通过开放模型的代码和数据集,他们加速了整个AI社区的发展,促进了创新和技术的民主化。这一阵营的代表有Meta AI、浪潮信息等。
-
另一类则是保持模型闭源的公司,它们通常将模型作为核心竞争力,用于提供专有服务或产品,以维持商业优势。闭源模型通常伴随着专有技术和服务,企业可以通过API等方式提供给客户使用,而不直接公开模型的细节或代码。这种模式有助于保障公司的商业利益,同时也为用户提供了稳定和安全的服务。这一阵营的代表有OpenAI、百度等。
无论是开源还是闭源,这些大模型都在推动人工智能领域向前发展,对于推动大语言模型技术的渐进式发展起到了至关重要的作用。
4 . 大模型时代挖掘模型能力的开发范式
进入大模型时代,人工智能领域的边界正以前所未有的速度扩展,而如何充分挖掘大模型的内在潜能,成为了应用开发者面前的一道关键课题。
在这一背景下,不同的应用场景催生了多样化的应用开发策略,这些策略不仅展现了大模型应用开发的丰富可能性,也预示着未来AI技术在各行业落地的广阔前景。
1 . 有手就行的Prompt工程
Prompt工程(Prompt Engineering)是指通过精心构造提示(Prompt),直接调教大模型,解决实际问题。
为了更充分地挖掘大模型的潜能,出现了以下两种技术:
上下文学习(In-Context Learning, ICL):将任务说明及示例融入提示文本之中,利用模型自身的归纳能力,无需额外训练即可完成新任务的学习。
思维链提示(Chain-of-Thought, CoT):引入连贯的逻辑推理链条至提示信息内,显著增强了模型处理复杂问题时的解析深度与广度。
2 . Embedding辅助给LLM外接大脑
尽管大模型具有非常出色的能力,然而在实际应用场景中,仍然会出现大模型无法满足我们需求的情况,主要有以下几方面原因:
知识局限性:
大模型的知识来源于训练数据,而这些数据主要来自于互联网上已经公开的资源,对于一些实时性的或者非公开的,由于大模型没有获取到相关数据,这部分知识也就无法被掌握。
数据安全性:
为了使得大模型能够具备相应的知识,就需要将数据纳入到训练集进行训练。然而,对于企业来说,数据的安全性至关重要,任何形式的数据泄露都可能对企业构成致命的威胁。
大模型幻觉:
由于大模型是基于概率统计进行构建的,其输出本质上是一系列数值运算。因此,有时会出现模型“一本正经地胡说八道”的情况,尤其是在大模型不具备的知识或不擅长的场景中。
因此,将知识提前转成Embedding向量,存入知识库,然后通过检索将知识作为背景信息,这样就相当于给LLM外接大脑,使大模型能够运用这些外部知识,生成准确且符合上下文的答案,同时能够减少模型幻觉的出现。
3 . 参数高效微调
在实际应用场景中,大模型还会经常出现以下问题:
大模型在当前任务上能力不佳,如果提升其能力?
另外,怎么使大模型学习其本身不具备的能力呢?
这些问题的答案是模型微调。
模型微调也被称为指令微调(Instruction Tuning)或者有监督微调(Supervised Fine-tuning, SFT),首先需要构建指令训练数据,然后通过有监督的方式对大模型的参数进行微调。经过模型微调后,大模型能够更好地遵循和执行人类指令,进而完成下游任务。
然而,由于大模型的参数量巨大, 进行全量参数微调需要消耗非常多的算力。为了解决这一问题,研究者提出了参数高效微调(Parameter-efficient Fine-tuning),也称为轻量化微调 (Lightweight Fine-tuning),这些方法通过训练极少的模型参数,同时保证微调后的模型表现可以与全量微调相媲美。
5 . 大模型应用开发 必知必会
通常,一个完整的大模型应用包含一个客户端和一个服务端。
客户端接收到用户请求后,将请求输入到服务端,服务端经过计算得到输出后,返回给客户端回复用户的请求。
1 . 客户端
在大模型应用中,客户端需要接受用户请求,并且能将回复返回给用户。
目前,客户端通常使用 Gradio 和 Streamlit 进行开发。
Gradio 基本概念
Gradio 有输入输出组件、控制组件、布局组件几个基础模块,其中
-
输入输出组件用于展示内容和获取内容,如:
Textbox
文本、Image
图像 -
布局组件用于更好地规划组件的布局,如:
推荐使用Column
(把组件放成一列)、Row
(把组件放成一行)gradio.Blocks()
做更多丰富交互的界面,gradio.Interface()
只支持单个函数交互 -
控制组件用于直接调用函数,无法作为输入输出使用,如:
Button
(按钮)、ClearButton
(清除按钮)
Gradio的设计哲学是将输入和输出组件与布局组件分开。输入组件(如
Textbox
、Slider
等)用于接收用户输入,输出组件(如Label
、Image
等)用于显示函数的输出结果。而布局组件(如Tabs
、Columns
、Row
等)则用于组织和排列这些输入和输出组件,以创建结构化的用户界面。
Streamlit 基础概念
-
Streamlit每个组件都是独立的,需要用什么直接查看官方文档即可,大致有如下几种组件:
-
-
页面元素
-
文本
-
数据表格
-
图标绘制(柱状图,散点图等等)
-
输入(文本框,按钮,下拉框,滑块,复选框,文件上传,等等)
-
多媒体(图片,音频,视频)
-
布局和容器
-
Chat(聊天对话控件)
-
状态(进度条,加载中,等等元素)
-
第三方组件(提供了更加丰富的组件)
-
-
应用逻辑
-
导航和页面(可以切换页面)
-
执行流程
-
缓存和状态
-
连接和加密(可连接数据库,也可以对内容进行加密处理)
-
自定义组件
-
公共组件(用户信息存储,帮助,以及输出html)
-
Config(使用配置文件,来定义一些内容)
-
-
工具
-
应用测试
-
命令行
-
-
-
2 . 服务端
在大模型应用中,服务端需要与大模型进行交互,大模型接受到用户请求后,经过复杂的计算,得到模型输出。
目前,服务端主要有以下两种方式:
-
直接调用大模型API:将请求直接发送给相应的服务商,如openai,讯飞星火等,等待API返回大模型回复
✔️ 优点:
-
便捷性: 不需要关心模型的维护和更新,服务商通常会负责这些工作。
-
资源效率: 避免了本地硬件投资和维护成本,按需付费,灵活调整成本支出。
-
稳定性与安全性: 专业团队管理,可能提供更好的系统稳定性和数据安全性措施。
-
扩展性: API服务易于集成到现有的应用和服务中,支持高并发请求。
✖️ 缺点:
-
网络延迟: 需要稳定的网络连接,可能会受到网络延迟的影响。
-
数据隐私: 数据需要传输到服务商的服务器,可能涉及数据安全和隐私问题。
-
成本控制: 高频次或大量数据的调用可能会导致较高的费用。
-
依赖性: 受制于服务商的政策变化,如价格调整、服务条款变更等。
-
-
大模型本地部署:在本地GPU或者CPU上,下载模型文件,并基于推理框架进行部署大模型
✔️ 优点:
-
数据主权: 数据完全在本地处理,对于敏感数据处理更为安全。
-
性能可控: 可以根据需求优化配置,减少网络延迟,提高响应速度。
-
成本固定: 初始投入后,长期运行成本相对固定,避免了按使用量付费的不确定性。
-
定制化: 更容易针对特定需求进行模型微调或扩展。
✖️ 缺点:
-
硬件投资: 需要强大的计算资源,如高性能GPU,初期投资成本较高。
-
运维复杂: 需要自行管理模型的更新、维护和故障排查。
-
技术门槛: 对于非专业团队而言,模型的部署和优化可能较为复杂。
-
资源利用率: 在低负载情况下,本地硬件资源可能无法充分利用。
-
综上,选择哪种方式取决于具体的应用场景、数据敏感性、预算以及对延迟和性能的需求。
baseline 代码精读
1 . 完整 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)
2 . baseline 代码逐句解释
# 导入所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM # 从 transformers 库中导入 AutoTokenizer(用于对文本进行分词和编码)和 AutoModelForCausalLM(用于因果语言模型)这两个类
import torch # 导入 torch 库,这通常用于深度学习中的张量操作和模型训练
import streamlit as st # 导入 streamlit 库,用于创建交互式的 Web 应用程序界面
# 创建一个标题和一个副标题
st.title("💬 Yuan2.0 智能编程助手") # 使用 streamlit 库创建了一个页面标题,标题的内容是“💬 Yuan2.0 智能编程助手”。它会在应用的页面顶部显示这个标题,为用户提供一个直观的应用名称和主题提示
# 源大模型下载
from modelscope import snapshot_download # 从 modelscope 库中导入了 snapshot_download 函数。这个函数通常用于下载模型的快照或预训练模型
model_dir = snapshot_download('IEITYuan/Yuan2-2B-Mars-hf', cache_dir='./') # 调用了 snapshot_download 函数来下载名为 IEITYuan/Yuan2-2B-Mars-hf 的模型,并将模型存储在当前目录(./)作为缓存;下载完成后,将模型的存储路径赋值给变量 model_dir ,以便后续代码可以使用这个路径来加载和使用模型
# 定义模型路径
path = './IEITYuan/Yuan2-2B-Mars-hf' # 定义了一个字符串变量 path ,并将其赋值为 './IEITYuan/Yuan2-2B-Mars-hf' 。这个路径可能是前面下载的模型的保存位置,后续代码中可能会基于这个路径来加载和使用相关模型
# 定义模型数据类型
torch_dtype = torch.bfloat16 # A10 #定义了一个变量 torch_dtype ,并将其赋值为 torch.bfloat16;torch.bfloat16 是 PyTorch 中的一种数据类型,常用于在特定的硬件环境(如 A10 显卡)中进行计算,以提高性能和节省内存。在后续与模型相关的操作中,可能会使用这个数据类型来配置模型的参数或进行计算
# torch_dtype = torch.float16 # P100 # 将变量 torch_dtype 赋值为 torch.float16 。这表示指定了一种不同的数据类型,可能适用于特定的硬件环境(如 P100 显卡)
# 定义一个函数,用于获取模型和tokenizer
@st.cache_resource #@st.cache_resource 是 streamlit 库提供的一个装饰器;它的主要作用是用于缓存函数返回的资源,例如模型、数据加载器等。这样,如果后续多次调用这个被装饰的函数,只要输入参数没有变化,就不会重新执行函数来获取资源,而是直接使用之前缓存的结果,从而提高应用的性能和效率
def get_model():# 定义了一个名为 get_model 的函数
print("Creat tokenizer...") # 在控制台输出字符串 "Creat tokenizer..." ,用于提示正在创建分词器的操作开始(表示正在创建分词器(tokenizer))
tokenizer = AutoTokenizer.from_pretrained(path, add_eos_token=False, add_bos_token=False, eos_token='<eod>') # 用 AutoTokenizer 类从预训练的模型所在的路径(由变量 path 指定)加载一个分词器(tokenizer);其中的参数:add_eos_token=False 表示不添加结束标记(End Of Sequence token);add_bos_token=False 表示不添加开始标记(Begin Of Sequence token);eos_token='<eod>' 表示将结束标记自定义为 <eod>
#这行代码向已加载的分词器 tokenizer 中添加了一系列自定义的特殊标记(<sep> 、 <pad> 等);参数 special_tokens=True 表示这些添加的标记被视为特殊标记,以便在后续的分词和编码过程中进行特殊处理
print("Creat model...")#在控制台输出字符串 "Creat model..." ,用于提示正在创建模型的操作开始(表示正在创建模型)
model = AutoModelForCausalLM.from_pretrained(path, torch_dtype=torch_dtype, trust_remote_code=True).cuda()#这行代码从预训练模型的路径(由变量 path 指定)加载一个因果语言模型(AutoModelForCausalLM)具体的参数含义如下:torch_dtype=torch_dtype:指定模型的数据类型为之前定义的 torch_dtype;trust_remote_code=True:表示信任远程代码;.cuda():将模型移动到 GPU 上进行计算,以提高计算速度;最终,加载并配置好的模型被赋值给变量 model ,以便后续在代码中使用
print("Done.") # 在控制台输出字符串 "Done." ,表示模型和分词器的创建过程已经完成
return tokenizer, model # 让函数 get_model 返回创建好的分词器 tokenizer 和模型 model,当在其他部分调用 get_model 函数时,可以接收到这两个返回值,以便进行后续的操作和使用
# 加载model和tokenizer
tokenizer, model = get_model() # 调用了前面定义的 get_model 函数,并将其返回的两个值,即分词器 tokenizer 和模型 model ,分别赋值给当前作用域中的变量 tokenizer 和 model;在后续的代码中,就可以直接使用这两个变量来进行与模型和分词相关的操作
# 初次运行时,session_state中没有"messages",需要创建一个空列表
if "messages" not in st.session_state: # 检查 st.session_state 中是否不存在名为 "messages" 的键
st.session_state["messages"] = [] # 如果不存在,就创建一个键为 "messages" ,值为一个空列表 [] 的项;这样做是为了在首次运行或 st.session_state 中尚未创建 "messages" 这个键值对时,确保有一个用于存储消息的空列表可用,以便后续代码可以向其中添加和操作消息数据
# 每次对话时,都需要遍历session_state中的所有消息,并显示在聊天界面上
for msg in st.session_state.messages: # 遍历 st.session_state.messages 列表中的每一个元素(这里的元素被称为 msg )
st.chat_message(msg["role"]).write(msg["content"])#对于每个 msg ,通过 st.chat_message(msg["role"]) 创建一个与 msg 中 role 对应的聊天消息对象,然后使用 .write() 方法将 msg 中的 content 内容显示出来,通常用于在一个聊天界面中逐个展示已存储的消息及其对应的角色
# 如果用户在聊天输入框中输入了内容,则执行以下操作
if prompt := st.chat_input():# 它的作用是先尝试从 st.chat_input() 获取用户的输入,如果获取成功(即输入不为空),就将获取到的值赋给变量 prompt ,然后执行后续代码块中的逻辑。如果获取不到用户输入(st.chat_input() 返回空),则不会执行后续代码块
# 将用户的输入添加到session_state中的messages列表中
st.session_state.messages.append({"role": "user", "content": prompt})#将一个包含用户输入信息的字典添加到 st.session_state.messages 列表中;添加的字典具有两个键值对:"role": "user" 表示这个消息的角色是用户;"content": prompt 表示消息的内容是用户通过 st.chat_input() 输入的 prompt;这样,st.session_state.messages 列表就记录了用户输入的消息及其角色,以便后续在聊天界面中进行展示或用于其他与聊天交互相关的处理
# 在聊天界面上显示用户的输入
st.chat_message("user").write(prompt)#在聊天界面中创建一个代表用户的消息框,并在其中显示用户输入的 prompt 内容:st.chat_message("user") 创建了一个标识为用户的聊天消息对象,然后 .write(prompt) 方法将 prompt 的内容写入这个消息框中进行展示
# 调用模型
prompt = "<n>".join(msg["content"] for msg in st.session_state.messages) + "<sep>" # 拼接对话历史 #对 st.session_state.messages 列表中每个消息的 content 值进行处理:使用生成器表达式 msg["content"] for msg in st.session_state.messages 遍历列表中的每个消息,并获取其 content 。然后使用 "<n>".join(...) 将这些 content 用 <n> 连接起来。最后再在连接后的字符串末尾添加 <sep>
inputs = tokenizer(prompt, return_tensors="pt")["input_ids"].cuda() # 这行代码首先使用之前创建的分词器 tokenizer 对 prompt 进行处理:tokenizer(prompt, return_tensors="pt") 会对 prompt 进行分词和编码,并指定返回 PyTorch 张量(pt)格式的结果。然后通过 ["input_ids"] 提取出其中的输入标识(input_ids);最后,使用 .cuda() 将这些输入标识移动到 GPU 上,以便利用 GPU 进行加速计算
outputs = model.generate(inputs, do_sample=False, max_length=1024) # 设置解码方式和最大生成长度 # 这行代码使用之前加载的模型 model 基于输入 inputs 进行生成操作,参数说明:do_sample=False 表示在生成过程中不进行采样,即按照概率分布选择输出的方式被禁用,生成结果更具确定性;max_length=1024 限制了生成的输出的最大长度为 1024 个标记(tokens);生成的结果被存储在 outputs 变量中
output = tokenizer.decode(outputs[0]) # 这行代码使用分词器 tokenizer 对模型生成的输出 outputs[0] 进行解码操作,解码的目的是将模型生成的数字编码形式的输出转换为人类可读的自然语言文本。通过解码,我们可以将模型生成的标记序列转换为有意义的句子或段落
response = output.split("<sep>")[-1].replace("<eod>", '') # 对解码得到的 output 进行了一系列处理:output.split("<sep>") 按照 <sep> 对字符串 output 进行分割,得到一个字符串列表;[-1] 选取这个列表中的最后一个元素,这意味着获取了以 <sep> 分割后的最后一部分内容;.replace("<eod>", '') 将选取的最后一部分内容中的 <eod> 字符串替换为空字符串;最终,处理后的结果被赋值给 response 变量
# 将模型的输出添加到session_state中的messages列表中
st.session_state.messages.append({"role": "assistant", "content": response}) # 将模型生成的响应以特定的格式添加到 st.session_state.messages 列表中,添加的字典 {"role": "assistant", "content": response} 表示这是助手(assistant)角色的回复,回复的内容是 response,这样做是为了在会话状态中保存助手的回复,以便在聊天界面中进行展示,或者用于后续的处理和交互
# 在聊天界面上显示模型的输出
st.chat_message("assistant").write(response) # 在聊天界面中创建一个代表助手(assistant)的消息框,并在其中显示模型生成的响应内容 response,st.chat_message("assistant") 创建了一个标识为助手的聊天消息对象,然后 .write(response) 方法将 response 的内容写入这个消息框中进行展示,以向用户呈现助手的回答
3 . baseline方案设计
1 . 概要设计
baseline基于源大模型的编程能力来解决用户的问题,主要包含一个Streamlit开发的客户端,以及一个部署好浪潮源大模型的服务端。客户端接收到用户请求后,首先进行交互历史拼接,然后输入到服务端的浪潮源大模型,得到模型输出结果后,返回给客户端,用于回复用户的问题。
2 . 详细设计
-
导入库: 导入所需要的依赖,包括
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,由于是从魔搭直接进行下载,速度会非常快。下载完成后,会在当前目录增加一个名为 IExiaITYuan
的文件夹,其中 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)
在聊天界面上进行显示