Yangyehan&UndGround.

全参模型加载

Word count: 2.5kReading time: 10 min
2024/07/24

全参数模型加载

调用模型代码-qwen_1.5-7B为例

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
105
106
107
108
109
110
111
112
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from fastapi import FastAPI, Depends
from pydantic import BaseModel
import uvloop
from fastapi.middleware.cors import CORSMiddleware
uvloop.install()

device = "cuda" # 设备名称

# 将模型和分词器的初始化移动到全局作用域
model_name = "output/firefly-qwen-7b-sft-full"
model = AutoModelForCausalLM.from_pretrained(
model_name,
low_cpu_mem_usage=True,# 表示加载模型的时尽量减少CPU内存的使用
torch_dtype=torch.float16, # 数据类型的参数,制定为fp16格式加载数据
device_map='auto' # 指定模型的各个部分加载到哪些设备上,auto表示自动加载到可用的GPU上,自动分配资源
)

tokenizer = AutoTokenizer.from_pretrained(model_name)# 加载模型的分词器

class MakeConversation:
def __init__(self):
self.max_new_tokens = 1024
self.do_sample = False
self.temperature = 0.1
self.repetition_penalty = 1.0 # 设置惩罚参数,用于减少生成文本中的重复性。值为1.0表示没有惩罚,值大于1.0表示会增大重复惩罚

def conversation(self, query):
stop_token_id = tokenizer.encode('<|im_end|>', add_special_tokens=False)
# 将停止标记<|im_end|>编码为stop_token_id,其中add_special_tokens=False表示,不需要额外添加一些特殊的tokens,
# 默认情况下,分词器可能会添加一些特殊 token,例如句子开头的 [CLS] 或结尾的 [SEP] token。通过设置 add_special_tokens=False,我们可以避免这些特殊 token 的自动添加,仅对提供的字符串进行编码。
assert len(stop_token_id) == 1
# 确保停止符被编码为一个长度
stop_token_id = stop_token_id[0]

prompt = f"""{query}"""

messages = [
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(device)
# return_tensors="pt"表示将text转换为tokenID,并返回一个pytorch格式的张量
# 这里的Token ID 指的是,分词对应词表的ID,而非分好的词
# .to(device)表示将张量移动到指定的GPU上
# 假设 text 是 "Hello, how are you?"
# tokenizer([text], return_tensors="pt") 将文本 "Hello, how are you?" 编码为 token ID,并返回一个包含这些 token ID 的 PyTorch 张量。
# 例如,如果文本编码后的 token ID 为 [15496, 11, 703, 389, 345],则生成的张量形状可能是 (1, 5),其中 1 是批量大小(batch size),5 是序列长度。

generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=self.max_new_tokens,
temperature=self.temperature,
repetition_penalty=self.repetition_penalty,
do_sample=self.do_sample,
eos_token_id=stop_token_id
)
# 从生成中去除输入的tokens
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
# batch_decoder是一个用于将多个token序列转回文本的数
# skip_special_tokens=True: 这个参数告诉 batch_decode 在解码时跳过特殊 token(例如模型生成过程中使用的 <|im_end|>作为结束的标记),从而只保留有意义的文本内容。
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

return response

app = FastAPI()

# 允许的来源
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

class Query(BaseModel):
query: str

# 使用Depends来创建一个MakeConversation的实例
make_conversition = Depends(MakeConversation)

@app.post("/conversation/")
async def conversation(query: Query, conv: MakeConversation = make_conversition):
print("输入'exit'或'quit'来退出服务器部署。")
while True:
if query.query.lower() == 'exit' or query.query.lower() == 'quit':
print("退出服务。")
break
response = conv.conversation(query.query)
return {"response": response}

# if __name__ == "__main__":

# qwen = MakeConversation()
# while True:
# print("请输入病情、舌诊和面诊的情况,用逗号分隔,输入完成后按回车键结束。输入'exit'或'quit'来退出循环。")
# query = input()
# if query.lower() == 'exit' or query.lower() == 'quit': # 检测退出命令
# print("退出循环。") # 可选:在退出时显示一个确认信息
# break # 如果用户输入'exit'或'quit',则退出循环
# response = qwen.conversation(query) # 假设conversation是正确的方法名
# print(response)
# print("\n")

uvloop ,只需在使用FastAPI之前安装uvloop即可

使用 uvloop 可以显著提高异步 I/O 操作的性能,特别适合网络密集型应用。在项目中安装和使用 uvloop 非常简单,只需要调用 uvloop.install(),并确保在应用启动之前完成安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from fastapi import FastAPI
import uvloop

# 安装 uvloop
uvloop.install()

app = FastAPI()

@app.get("/")
async def read_root():
return {"message": "Hello, World!"}

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}

# 运行 FastAPI 应用(在实际使用时,通常使用 uvicorn 或其他 ASGI 服务器)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

在模型进行Next Token Prediction的时候,do_sample参数指:

do_sample 参数在文本生成过程中决定了是否使用采样方法来生成文本。具体来说,它控制模型在生成下一个 token 时的选择策略。

**do_sample 参数的作用:**

1.	**do_sample=False(默认值)**:

•	当 do_sample 被设置为 False 时,模型使用贪婪搜索(Greedy Search)策略生成文本。

•	贪婪搜索每一步都选择概率最高的下一个 token。这种方法确保生成的文本是概率最大的序列。

•	贪婪搜索的结果是确定性的,同样的输入总是会得到相同的输出,因为它不包含随机性。

2.	**do_sample=True**:

•	当 do_sample 被设置为 True 时,模型使用采样(Sampling)策略生成文本。

•	采样策略在生成下一个 token 时,从预测的概率分布中随机选择一个 token。被选择的 token 的概率与其预测概率成正比。

•	这种方法引入了随机性,因此同样的输入可以生成不同的输出,增加了生成的多样性和创造性。

**采样策略:**

•	**温度(Temperature)**:

•	temperature 参数配合采样使用,可以控制采样的随机性程度。较高的温度会使采样的结果更加随机,较低的温度会使结果更加确定性。

•	**Top-k 采样**:

•	在 Top-k 采样中,模型只从前 k 个概率最高的 token 中采样。这个方法可以过滤掉低概率的 token,从而提高生成文本的质量。

•	**Top-p(Nucleus)采样**:

•	在 Top-p 采样中,模型从累积概率达到 p 的最小集合中采样。这个方法根据概率分布动态确定采样范围,通常比 Top-k 采样更灵活。

generarted_ids解析

1
2
3
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
1. **zip(model_inputs.input_ids, generated_ids)**: • model_inputs.input_ids 是输入给模型的 token 序列。 • generated_ids 是模型生成的完整序列,包含输入 token 和新生成的 token。 • zip 函数将 model_inputs.input_ids 和 generated_ids 两个列表按元素配对,形成一个由元组组成的迭代器,每个元组包含一个输入序列和相应的生成序列。 2. **列表解析**: • 列表解析用于遍历 zip 生成的元组,并从每个 output_ids(生成的序列)中去掉输入序列部分,提取新生成的部分。 3. **output_ids[len(input_ids):]**: • 对于每个 input_ids 和 output_ids(生成的序列): • len(input_ids) 是输入序列的长度。 • output_ids[len(input_ids):] 切片操作用于从 output_ids 中去掉前 len(input_ids) 个 token,只保留新生成的 token。 **示例** 假设我们有以下输入和生成的序列: • 输入序列(input_ids): [1, 2, 3] • 生成的序列(generated_ids): [1, 2, 3, 4, 5, 6] 代码将会进行如下操作: • zip 会形成配对: [(input_ids, generated_ids)] • 对于每个配对,output_ids[len(input_ids):] 会变成 [4, 5, 6],因为 len(input_ids) 是 3。 最终结果是一个新的 generated_ids 列表,只包含新生成的 token,而不包含输入的 token。

Depends

**Depends 函数**

•	Depends 是 FastAPI 中用于声明依赖的函数。当 FastAPI 看到某个路径操作(即 API 端点)依赖某个对象时,它会自动创建和管理该依赖的实例。

•	Depends 的参数是一个可调用对象,如函数或类的构造函数。

**MakeConversation 类**

假设我们有一个名为 MakeConversation 的类:

1
2
3
4
5
6
7
8
9
10
class MakeConversation:
def __init__(self):
self.max_new_tokens = 1024
self.do_sample = False
self.temperature = 0.1
self.repetition_penalty = 1.0

def conversation(self, query):
# 这里是对话生成的逻辑
pass
**make_conversition = Depends(MakeConversation)** • Depends(MakeConversation) 告诉 FastAPI 需要创建一个 MakeConversation 类的实例。 • 这种依赖会在每次请求时被自动创建和管理。 **在路径操作中使用**
1
2
3
4
@app.post("/conversation/")
async def conversation(query: Query, conv: MakeConversation = make_conversition):
response = conv.conversation(query.query)
return {"response": response}
在上面的代码中: 1. **路径操作函数 conversation**: • 这是一个处理 /conversation/ 路径的 POST 请求的函数。 • query 参数是一个 Query 类型的实例,包含了请求体中的数据。 • conv 参数是一个 MakeConversation 类型的实例,由 FastAPI 自动创建并注入。 2. **依赖注入**: • conv: MakeConversation = make_conversition 这部分代码表示 conv 依赖于 MakeConversation 类的实例。 • 当这个路径操作被调用时,FastAPI 会自动创建一个 MakeConversation 实例并将其作为 conv 参数传递给路径操作函数。

async协程

async 关键字在 Python 中用于定义异步函数,通常称为协程(coroutine)。协程是一种可以在执行过程中暂停并在稍后恢复执行的函数,允许在等待某些操作完成时进行其他操作。这对于处理 I/O 操作(如网络请求、文件读取等)非常有用,因为这些操作可能会阻塞程序的执行。

1
2
3
4
5
6
7
8
async def conversation(query: Query, conv: MakeConversation = make_conversition):
print("输入'exit'或'quit'来退出服务器部署。")
while True:
if query.query.lower() == 'exit' or query.query.lower() == 'quit':
print("退出服务。")
break
response = conv.conversation(query.query)
return {"response": response}
• **async def conversation**: • 这定义了一个异步函数 conversation。 • 异步函数在遇到 I/O 操作时不会阻塞整个程序的执行,而是允许其他任务继续执行。 **异步函数的优势** • **非阻塞**: • 异步函数在执行耗时操作(如 I/O 操作)时不会阻塞其他任务的执行,提高了程序的效率和响应能力。 • **提高性能**: • 特别是在处理大量 I/O 操作时,异步函数可以显著提高程序的性能。
CATALOG
  1. 1. 全参数模型加载
  2. 2. 调用模型代码-qwen_1.5-7B为例
    1. 2.1. uvloop ,只需在使用FastAPI之前安装uvloop即可
    2. 2.2. 在模型进行Next Token Prediction的时候,do_sample参数指:
    3. 2.3. generarted_ids解析
    4. 2.4. Depends
    5. 2.5. async协程