Intership|LLM应用从训练到构建
以下有很多东西已脱密处理,可读性会降低。在咨询+1 得到同意后,把一些流程记录下来。
1 LLM预训练中的参数设置以及训练设置
1.1 参数设置
1 |
|
- stage:指令监督微调
- model_name_or_path:模型我们选择Baichuan2
- finetuning_type:选择lora
- per_device_eval_batch_size:目前使用的3090只支持1 batch per gpu
- learning_rate:经过多次尝试后,发现5e-5的学习率比较适合我们的任务
- num_train_epochs:训练轮次可以根据数据量的多少来设置
1.2 训练加速
加速可以选择Accelerate、DeepSpeed
在这里我们选择了DeepSpeed,DeepSpeed可以通过zero技术增加通信代价来减少显存消耗,但是我们的3090每张卡可以单独放下一个batch,所以我们使用zero 0的设置,实际上就为普通的DDP多卡并行。不同Zero的设置大致效果如下:
速度上:阶段 0 (DDP) > 阶段 1 > 阶段 2 > 阶段 3
显存上:阶段 0 (DDP) < 阶段 1 < 阶段 2 < 阶段 3
需要增加以下依赖
1 |
|
zero 0的deepspeed配置文件如下
1 |
|
使用deepspeed对应的训练启动命令也要修改,如果使用单机8卡的机器,可以使用以下命令启动
1 |
|
1.3 完整一次训练流程
1.3.1 训练
1 |
|
1.3.2 导出
1 |
|
1.3.3 量化
目前资源够使用,没有使用量化模型的步骤,如需,可以使用chatglm进行量化
1.3.4 api部署
需要对api_demo.py进行修改,修改如下
1 |
|
1 |
|
2 数据集构造
2.1 构造样式
由于我们实现的功能有几种,所以数据集的构造也有一定的细微差别,主要体现在input prompt上
2.1.1 每种类型格式
prompt构造
此处删除
2.1.2 构造原因
分为3种
input中不需要额外prompt:比如classify和extract,这两种llm的实现不需要外界额外信息,因此不需要额外的prompt。
input中需要一种额外prompt:比如head和point,这两种的实现只需要少量外界额外信息,因此只需要补充上额外的信息即可。
input中需要两种额外prompt:比如line和surface,这两种军标中,每个小的军标都可能存在不同的算法,所以需要在额外基础信息的情况下,加上军标的补充说明去训练,比如下面这两种军标,存在完全不同的计算方法,所以针对某种军标需要单独设计数据去参与训练。
- 作战分界线中,0为不含1为含
- 燕尾箭头中,通过起点、经过点、终点确定,由于起点需要两个点,所以需根据南北、东西走向确定,如果是南北,则在起点的经度上进行+-0.005;如果是东西,则在起点的纬度上+-0.005
2.2 完整构造流程
一次完整的构造流程分为生成数据->校准数据->转化数据
2.2.1 生成数据
生成数据我们使用gpt-4-turbo-preview,之所以使用gpt-4-turbo-preview是因为它相比于其他模型具有限定json格式输出的功能,其tokens长度也完全足够我们使用。与gpt4相比,他的单位tokens也更便宜,如下,gpt3.5没有限制json输出的功能,综合来讲选择gpt-4-turbo-preview比较合理。
Model | Input | Output |
---|---|---|
gpt-4-0125-preview | $10.00 / 1M tokens | $30.00 / 1M tokens |
gpt-4-1106-preview | $10.00 / 1M tokens | $30.00 / 1M tokens |
gpt-4-1106-vision-preview | $10.00 / 1M tokens | $30.00 / 1M tokens |
gpt-4 | $30.00 / 1M tokens | $60.00 / 1M tokens |
gpt-4-32k | $60.00 / 1M tokens | $120.00 / 1M tokens |
gpt-3.5-turbo-0125 | $0.50 / 1M tokens | $1.50 / 1M tokens |
gpt-3.5-turbo-instruct | $1.50 / 1M tokens | $2.00 / 1M tokens |
classify的生成代码如下,其余的代码在gitlab中 | ||
另外在生成数据的时候,可能要注意以下几点: |
可以人工给出的信息,尽量提前给出:大模型可能会有很强的某种数据的倾向,尽管这些倾向有时候看起来匪夷所思,比如示例军标我们给出一个直升机,让他随机,他就可能倾向于战斗机等等的其他种类的飞机,而忽略其他军标,所以随机数据上减少对gpt的使用,比如ID可以使用uuid,军标可以通过python随机提前给出,这样gpt生成的效果会更好。
信息与信息有强相关性,就要一并给出:军标有中文和英文,生成数据的时候就要一并给出,不能让gpt自己猜测,防止生成的数据训练出来的模型未来也使用猜测的英文。
数学逻辑特别要注意:比如某点南北展开,对应我们的点需要纬度上+-0.005,这样的在生成的时候可以分开生成,先强制全部生成南北的,后续我们重新给出东西的例子,再重新生成东西。可以避免后续的矫正。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import os
from openai import OpenAI
from dotenv import load_dotenv
import json
from tqdm import tqdm
# Load environment variables
load_dotenv()
# Initialize OpenAI client
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY"),
)
folder_path = 'data'
# Number of data entries to generate
n = 50 # Adjust n to the desired number of data entries
# Loop to generate and print n data entries
for i in tqdm(range(n)):
# Define the prompt for each iteration
prompt = f"""
生成第{i+1}条数据。以下是一个示例和指令:
我给你一个例子,需要生成新的json数据
{{
"instruction" : "*******",
"input" : "*********",
"output":
{{
******
}}
请生成更多这样的json数据,注意*****
"""
# Generate the completion for the current iteration
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": prompt,
}
],
model="gpt-4-turbo-preview",
response_format={ "type": "json_object" },
)
file_path = os.path.join(folder_path, f'{i+1}.json')
generated_content = chat_completion.choices[0].message.content if chat_completion.choices else 'No response'
# Save the generated data to a JSON file
with open(file_path, 'w', encoding='utf-8') as file:
# Assuming generated_content is a string in JSON format, directly write it
file.write(generated_content)生成好之后需要对数据进行拼接,将多个json拼接成一个完整的即可。
2.2.2 校准数据
校准数据就需要按照每种数据的需求,对关键点进行人工判断,修改出错的地方。
2.2.3 转化数据
经过测试,如果直接将json作为output输入给训练程序效果会不好。
可以使用json.dumps将output转化为字符串即可。
3 后端实现
在langchain和直接使用代码自己实现功能的两种选择中,选择直接自己用python实现相关功能,原因是:一是langchain中的向量数据库的知识库形式并不精准,在我们的应用中不能起到好的效果,二是如果使用langchain调用api没有必要,白白增加了一层应用,不如自己手动调用。
这里列举几个langchain使用的例子:针对特定文档的问答:根据给定的文档回答问题,使用这些文档中的信息来创建答案。Agents:开发可以决定行动、采取这些行动、观察结果并继续执行直到完成的代理。可以看出langchain是可以完成多agents功能,但是目前我们的任务流水执行暂时不需要让模型去选择调用哪个api,数据是单向流动,未来如果考虑减少使用到的模型的数量可能就需要使用langchain来做。
3.1 服务器api
服务器api这里需要实现一些查询功能以及计算的后处理功能。我选择Flask实现
查询部分:
查询部分的内容非常清楚,如下表
端点 | 方法 | 参数/请求体 | 返回内容 | 状态码 |
---|---|---|---|---|
/get_location |
GET | address : 地名列表,逗号分隔 |
JSON格式的地点信息列表 | 200: 成功 404: 未找到数据 |
/get_jb |
GET | name : 军标名称列表,逗号分隔 |
JSON格式的军标信息列表 | 200: 成功 404: 未找到数据 |
/add_location |
POST | JSON: 包含地名 、经度 、纬度 和高度 字段 |
确认信息 | 201: 成功添加 400: 请求中缺少JSON或数据 |
/update_location |
POST | JSON: 包含地名 、经度 、纬度 和高度 字段 |
确认信息 | 200: 成功更新 400: 请求中缺少JSON或数据 404: 未找到地点 |
/get_id |
GET | 无 | JSON格式,包含生成的随机ID | 200: 成功 |
计算部分:
计算部分我在这里举个例子:此处已删除
3.2 大模型api
端点 | 方法 | 摘要 | 请求体需求 | 响应类型及数据结构 |
---|---|---|---|---|
/v1/models |
GET | 列出模型 | 无 | 200: 成功,返回ModelList |
/v1/chat/completions |
POST | 创建聊天完成 | 需要,ChatCompletionRequest |
200: 成功,返回ChatCompletionResponse 422: 验证错误 |
使用python对大模型进行调用即可
3.3 本地python
本地python对于各个大模型、服务器api按照流水进行使用即可,可以注意以下几点:
在写整体的流水线处理的时候,因为每次调用都是输入文本->扩展prompt+调用插件->调用llm->结果的后处理。可以注意代码的复用,避免重复,未来也可以更好地扩展。
因为每次调用模型都要花费时间,所以加入日志记录模块可以很快的找到是输入、扩展prompt还是后处理哪一步出错了,日志中可以按我们处理的流程记录每一个文件生成的经过。
在遇到:营指挥所、指挥所 这种选择问题,可以考虑加入分词,比如jieba。预先创建分词数据库,使用最长前缀匹配可以很好的解决这种问题。在这个应用中,可以想到分词的功能可能不止这点,比如说在最初的文本输入后,实际上可以使用传统分词将所有的军标先提取出来,这样我们就得到了军标们和最初文本,然后每一次输入大模型的都是完整的文本,和军标之一,这样可以巧妙避免前后文相关的情况,但是同时对数据集的构造制造了更大的难度。
4 不足
- 数据集数量影响模型效果:特别是在line、surface中,不同军标都需要有一部分自己的数据集,生成合理的数据集是一个庞大的任务。
- 每种计算需要单独设计z:对于坐标的复杂计算,需要单独设计计算方式。
- 需要再增加一个llm专门用于对坐标的解释:比如有的坐标和南北方位有关,但是南北的表示又有很多不同,所以需要设计一个llm对类似的坐标方位进行一个固定的转化,目前demo阶段并未设计实现。
- 需要一种统一的文件:llm的表现和数据集的形式强相关,如果换一种文件输入,原来的数据集就需要重新生成,导致应用可用性会降低。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!