前言
TIP
感谢UCloud旗下的Compshare平台让我一个穷学生也可以用得起GPU云服务器x
RTX4090,16C 64G,共享100M带宽,200GB硬盘的配置每小时只要2.6r,按月开通的话每月一千二出头,折算下来每小时只有1.7r,属实是实惠到家了。有独立外网IP、支持GitHub和huggingface访问加速(可以快速拉取资源、完成模型部署)。学生还额外有八折,和我一样预算有限的可以考虑一下。我也是靠这家才有机会尝试实际部署这种模型。
前排提示
本篇适用于第一次尝试文字模型部署的朋友。末尾还有一个给没接触过编程的朋友们准备的小教程。大佬们完全不需要看这个,看了也没用x
在之前写的两篇文章:
【0基础】使用Mistral-7B模型搭建简易的聊天API(基础篇)-CSDN博客
【0基础】使用Mistral-7B模型搭建简易的聊天API(进阶篇)-CSDN博客
里,用到了如BitsAndBytesConfig等内容,但因为文章主要是让该领域的新手可以直接部署,并将AI接入自己的其他项目为主,并没有做什么说明。本篇对这两篇中的一些内容进行进一步的解释说明,并给出一些结合自己需求进行修改的建议。只做必要的解释用于实操,不太涉及原理。
此外,还准备了直接运行,不创建API的代码,支持流式输出和连续对话,给新手朋友们测试时一键使用。
基础篇的额外说明
硬件相关
对于部署模型来说,GPU某种意义上对效果起到决定性作用。按照我给的方法能否成功部署运行不仅取决于显存的大小(要大于8G),还有一些其他因素。例如:在文章中我们使用了nf4格式量化加载,nf4实际上是一种特殊的4位浮点数,可以表示比fp4更大的范围,精度损失比fp4要小。这种位数较低的格式,尤其是新兴格式,往往在新显卡上的表现会更好。而且CPU无法使用这类格式,这意味着使用CPU运算不仅效率更低,还需要比GPU运行时的显存消耗更多的内存(因为无法量化到4bit)。
代码解释
Transformers
Transformers 库是一个开源库,我们所使用的Mistral 7B模型是基于 transformer 模型结构的。其主要优点是效果相对好且可以并行训练。目前很多文字模型都基于Transformers。
BitsAndBytesConfig
BitsAndBytes可以在模型加载时直接进行量化。这种量化方式十分简单易用,而且很多模型都可用,使用BitsAndBytes量化可以不需要提前对模型进行任何其他的操作。其原理不多介绍,主要说一说它的参数:
先附上nf4量化的参数
nf4_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
load_in_4bit想必不需要做什么说明,因为BitsAndBytes还可以选择8bit量化,所以需要声明一下使用4bit。
bnb_4bit_quant_type="nf4" 这个参数表示量化的格式,建议所有新显卡都选择nf4,如果觉得效果不佳,可以改成fp4试试,但通常不会明显更好,只会损失更多精度。
bnb_4bit_use_double_quant=True 表示使用嵌套量化来提高内存效率
bnb_4bit_compute_dtype=torch.bfloat16 这种量化方式其实在计算时仍然会采用高精度的格式完成,这个参数表示在计算时采用bf16格式进行。默认使用的是float32,但单精度计算一般太慢,推理时没必要。
虽然也可以将参数改为8bit量化,但并不常用,因此不做介绍。
AutoModelForCausalLM
AutoModelForCausalLM实际上是自动加载不同类型的文本生成模型的方法,解释一下用到的参数:
-
use_safetensors=True 表示使用safetensors模型(看模型文件后缀就知道了)
-
quantization_config=nf4_config 表示使用我们刚刚创建的量化参数加载
Prompt
主要分为system_prompt和user_prompt。system_prompt正如文章中所讲的,可以根据实际使用场景进行修改,它会持续影响模型的生成。你可以用system_prompt为模型设立一个人设,比如“你是一个说话幽默有趣的AI助手”等等,以得到你希望的风格的输出。你甚至可以通过“你的作用是阅读文章并生成摘要”这样的system_prompt让模型在一定范围内实现不同的功能。
user_prompt实际上就是用户的输入,也是模型要回复的内容。
Tokens
token 通常是指一串字符或符号,在文本生成模型的场景下,它是处理文本的最小单元或基本元素,比如一个标点、一个词组、一个单词等等。在生成回复的时候使用的max_tokens参数实际上是限制了inputs_tokens+response_tokens的最大长度,防止生成出过长的无用回复持续占用算力。文章中给的限制是200~300,但实际上我个人一般设置为1000左右。此外,你还可以通过每秒生成的tokens数量大概衡量效率,例如使用tesla t4大概是13,4080移动版大概是25。
给完全无编程经验的朋友准备的流程和代码
在基础篇中虽然有直接运行的代码,但并不支持流式输出。网上有很多流式输出的教程、有很多4bit量化部署mistral 7B模型的教程,还有很多的让模型生成结合历史纪录的教程。但三者一起的代码似乎没多少成品。因此顺带附上这段教程,让完全无编程经验的朋友也可以体验自己部署模型并运行起来的乐趣,让大家能像体验AI绘画一样体验文字生成模型,过过瘾。(当然也可以选择使用前言中说的compshare.cn算力平台,自带丰富的镜像,有些自带模型可以直接使用,非常方便。)
首先,基础的模型下载仍然是参见基础篇开头。
接着,进行环境部署。
首先明确你的显卡型号,安装好对应版本的cuda。对于主流的30或40系显卡推荐安装cuda12.1。
接着,对于完全没有编程需求朋友来说,没必要折腾anaconda之类的虚拟环境,只需要在python官网下载安装python 3.11,然后打开Windows powershell。
按照我在基础篇中写明的环境进行配置。
配置过程:
首先在powershell里输入:
python -m pip install --upgrade pip
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
将软件包下载源替换为国内的清华镜像站。
接着,对每个软件包都用:
pip install 软件包名==版本
进行下载安装,就像基础篇截图里的格式那样。
然后找到你下载的模型文件,把它们都复制到一个文件夹里,文件夹命名为model。把model文件夹移动到另一个文件夹(假设文件夹叫AI)里,并在AI文件夹中创建一个记事本文件。
在该记事本中粘贴以下的代码:
from threading import Thread
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer, BitsAndBytesConfig
import torch
import time
# 建构显示对话
def build_prompt(hist):
prom = "clear 清空对话历史,stop 终止程序,set max tokens设置最大tokens"
for que, response in hist:
prom += f"\n\n用户:{que}"
prom += f"\n\nModel:{response}"
return prom
# 维护多轮历史
def build_history(hist, que, response, index):
hist[index] = [que, response]
return hist
def generator():
global streamer
global inputs
global max_tokens
attention_mask = torch.ones_like(inputs).to("cuda") # 创建与 input_ids 相同形状的全1张量
_ = model.generate(inputs, streamer=streamer, max_new_tokens=max_tokens, attention_mask=attention_mask,
pad_token_id=tokenizer.eos_token_id)
if __name__ == "__main__":
load_in_4bit = True
if load_in_4bit:
nf4_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
tokenizer = AutoTokenizer.from_pretrained("./model", trust_remote_code=True,
quantization_config=nf4_config)
model = AutoModelForCausalLM.from_pretrained("../mistralai/Mistral-7B-Instruct-v0.2",
trust_remote_code=True, quantization_config=nf4_config)
else:
tokenizer = AutoTokenizer.from_pretrained("../mistralai/Mistral-7B-Instruct-v0.2", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("../mistralai/Mistral-7B-Instruct-v0.2",
trust_remote_code=True).half().cuda()
model = model.eval()
# TextIteratorStreamer实现
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True)
history = []
turn_count = 0
max_tokens = 1000
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
print("clear 清空对话历史,stop 终止程序")
while True:
query = input("\n用户:")
if query.strip() == "stop":
break
if query.strip() == "clear":
history = []
turn_count = 0
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
print("clear 清空对话历史,stop 终止程序,set max tokens设置最大tokens")
continue
if query.strip() == "set max tokens":
max_tokens = int(input("输入新的最大token"))
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
print("clear 清空对话历史,stop 终止程序,set max tokens设置最大tokens")
continue
system_prompt = 'Your name is Yuki, an AI assistance. Your answer should be brief.'
B_INST, E_INST = "[INST]", "[/INST]"
prompt = system_prompt
for q, r in history:
prompt += "<s>"
prompt += B_INST
prompt += q
prompt += E_INST
prompt += r
prompt += "</s>"
if len(prompt) >= 30000:
print("警告:历史记录可能过长")
prompt = f"{prompt}{B_INST}{query.strip()}\n{E_INST}"
history.append([query, ""])
inputs = tokenizer.encode(prompt, return_tensors="pt").to("cuda")
thread = Thread(target=generator, args=())
thread.start()
st = time.time()
generated_text = ""
refresh_text = ""
count = 0
# 流式输出
print("Model:", end="")
for new_text in streamer:
generated_text += new_text
refresh_text += new_text
count += 1
if count == 1:
st = time.time()
if count % 2 == 0:
if count % 40 == 0:
history = build_history(history, query, generated_text, turn_count)
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
print(build_prompt(history), end="")
else:
print(refresh_text, end="")
refresh_text = ""
thread.join()
tokens = count
ed = time.time()
if generated_text[-4:] == "</s>":
generated_text = generated_text[0:-4]
history = build_history(history, query, generated_text, turn_count)
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
print(build_prompt(history))
print()
print("用时:", round(ed - st, 2))
print("输出Tokens:", tokens)
print("每秒生成的Tokens:", round(tokens / (ed - st), 4))
turn_count += 1
如果你认为自己的显卡实力足够,可以找到里面的load_in_4bit = True,改为load_in_4bit = False(一定要注意大小写)。
接着,在Windows资源管理器中:
如图中所示,勾选文件拓展名选项,并将新建记事本重命名为xxx.py。(xxx自己写)
最后,进如AI文件夹,右键鼠标打开Windows powershell,输入python xxx.py。
等模型加载进度条跑到头,显示“用户:”的时候,就可以开始对话啦!输入你的问题,按回车就可以开始生成回复。效果如图:
输入clear并回车可以清空对话的记忆,否则会结合之前的问题和回复进行对话。(关掉程序当然也是会丢失记忆的)
如果输出到一半就断了,可以输入set max tokens并回车,然后输入建议不大于2000的数字并回车,就可以修改单次生成的长度限制(可以理解为多少个词)。
结语和一些闲谈
不知道各位有没有这样的感觉,这几年计算机领域很多东西有了翻天覆地的变化。大数据、AI的使用越来越广泛,与日常生活的关系越来越密切。曾经的扫盲是识不识字,现在的扫盲是文化知识,未来的扫盲很可能也要涉及AI的使用。不仅仅是大厂的AI服务影响更多人,还需要更多人都粗略的知道AI、会自己简单的使用AI,就好像现在的AI画图一样,我相信未来的文字生成小参数量模型,比如像mistral 7B这样的模型也会被越来越多的使用。很多可以接入文字模型,但又不需要GPT那样的大型模型的场景,比如在课表APP里接入一个小模型用于总结出学生今天的一句话日程、在闹钟APP里为用户汇总出今天的注意事项和安排等等,这些需求很多都可以通过自己微调部署一个小模型解决。以上就是我的一些想法。
最后还是非常感谢UCloud所属的Compshare平台提供了便宜好用的GPU服务器,如果你想部署模型尝尝鲜,不妨也尝试一下这个平台,支持按时/天/月付费,还有很多部署好的镜像可以一键安装使用。
那么以上就是本篇的全部内容了。本人自己也在学习中,如有问题请各路大佬多加指点,感谢。如果有新手朋友遇到问题,欢迎留下评论,我会尽量回复。