系列目录-大模型学习篇
问题场景(Problems)
本地部署LLMs运行环境,能够完成大模型的推理、训练、量化的部署。
我是在服务器上进行部署的,相关配置如下:
服务器系统:CentOS 7.7
存储:约40T
显卡:A800 * 2(单卡显存 80 GB)
网络:校园网
虚拟环境:docker + miniconda
解决方案(Solution)
modelscope提供了部署所需的全部详细文档,这里是官网。
本章节采用modelscope官网给出的部署实例进行演示,Qwen1.5全流程最佳实践。
环境配置
创建python==3.10的conda虚拟环境(官网使用的是3.8版本,我使用的是3.10版本)。
conda create -n myenv python=3.10
conda activate myenv
安装modelscope库,使用清华源。常见的可用源还包括:
-i https://mirrors.bfsu.edu.cn/pypi/web/simple
-i https://mirrors.ustc.edu.cn/pypi/web/simple
等等,可以根据自己的网络条件自行选择。
pip install modelscope -i https://pypi.tuna.tsinghua.edu.cn/simple
# pip install modelscope[framework] -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install 'ms-swift[llm]' -U # 安装swift命令
由于我们需要使用vllm和autoawq提供推理加速和模型量化,而这两个库本身对cudnn版本和CUDA版本的要求不一致,并且他们针对torch的版本也有一定的要求,所以此处的环境安装会稍微复杂一点。
首先通过nvidia-smi和nvcc -V命令查看服务器支持的CUDA version版本和cudnn版本。
nvidia-smi # 对应GPU 驱动程序的版本,
nvcc -V # 对应CUDA Toolkit版本,即Cuda toolkit <= GPU 驱动程序版本
我的CUDA 驱动版本是12.4,理论上我最高可以安装12.4版本的CUDA Toolkit,但是经过测试我最后选了11.8才解决了各种依赖问题。
关于各自的安装依赖关系,可以参考以下资料:
如何安装CUDA toolkit和对应的cudnn
autoawq各发布版本
vLLMs版本介绍
我将自己测试后最终选择的版本放在这里,仅供读者参考:
cuda toolkit==11.8
autoawq==0.2.6
vllm==0.5.3
torch==2.3.1
modelscope==1.17.1
测试代码
根据官网的代码,本地新建文件:modelscope_Qwen1half7B_QA.py,代码内容直接copy官网的代码:
# Experimental environment: A800
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
from swift.llm import (
get_model_tokenizer, get_template, inference, ModelType,
get_default_template_type, inference_stream
)
from swift.utils import seed_everything
import torch
model_type = ModelType.qwen1half_7b_chat
template_type = get_default_template_type(model_type)
print(f'template_type: {template_type}') # template_type: qwen
kwargs = {}
# kwargs['use_flash_attn'] = True # 使用flash_attn
model, tokenizer = get_model_tokenizer(model_type, torch.float16,
model_kwargs={'device_map': 'auto'}, **kwargs)
# 修改max_new_tokens
model.generation_config.max_new_tokens = 128
template = get_template(template_type, tokenizer)
seed_everything(42)
query = '浙江的省会在哪里?'
response, history = inference(model, template, query)
print(f'query: {query}')
print(f'response: {response}')
# 流式
query = '这有什么好吃的?'
gen = inference_stream(model, template, query, history)
print_idx = 0
print(f'query: {query}\nresponse: ', end='')
for response, history in gen:
delta = response[print_idx:]
print(delta, end='', flush=True)
print_idx = len(response)
print()
print(f'history: {history}')
"""
[INFO:swift] model.max_model_len: 32768
[INFO:swift] Global seed set to 42
query: 浙江的省会在哪里?
response: 浙江省的省会是杭州市。
query: 这有什么好吃的?
response: 浙江有很多美食,比如杭州的西湖醋鱼、东坡肉、龙井虾仁,宁波的汤圆、奉化芋头羹,温州的鱼饼、楠溪江豆腐干,嘉兴的南湖菱角等等。每一道菜都有其独特的风味和历史背景,值得一试。
history: [['浙江的省会在哪里?', '浙江省的省会是杭州市。'], ['这有什么好吃的?', '浙江有很多美食,比如杭州的西湖醋鱼、东坡肉、龙井虾仁,宁波的汤圆、奉化芋头羹,温州的鱼饼、楠溪江豆腐干,嘉兴的南湖菱角等等。每一道菜都有其独特的风味和历史背景,值得一试。']]
"""
测试结果
我们可以看到模型直接加载权重后,输出了结果。
如果是第一次运行,那么会本地下载模型,保存路径为:
.cache/modelscope/hub/qwen/Qwen1___5-7B-Chat
vLLM推理加速
还是按照modelscope官网的代码,我们使用python推理qwen1half-7b-chat-awq, 这里我们使用VLLM进行推理加速:
# Experimental environment: A800 * 2
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
from swift.llm import (
ModelType, get_vllm_engine, get_default_template_type,
get_template, inference_vllm, inference_stream_vllm
)
import torch
model_type = ModelType.qwen1half_7b_chat_awq
llm_engine = get_vllm_engine(model_type, torch.float16, max_model_len=4096)
template_type = get_default_template_type(model_type)
template = get_template(template_type, llm_engine.hf_tokenizer)
# 与`transformers.GenerationConfig`类似的接口
llm_engine.generation_config.max_new_tokens = 512
request_list = [{'query': '你好!'}, {'query': '浙江的省会在哪?'}]
resp_list = inference_vllm(llm_engine, template, request_list)
for request, resp in zip(request_list, resp_list):
print(f"query: {request['query']}")
print(f"response: {resp['response']}")
# 流式
history1 = resp_list[1]['history']
query = '这有什么好吃的'
request_list = [{'query': query, 'history': history1}]
gen = inference_stream_vllm(llm_engine, template, request_list)
print_idx = 0
print(f'query: {query}\nresponse: ', end='')
for resp_list in gen:
request = request_list[0]
resp = resp_list[0]
response = resp['response']
delta = response[print_idx:]
print(delta, end='', flush=True)
print_idx = len(response)
print()
print(f"history: {resp_list[0]['history']}")
"""
query: 你好!
response: 你好!有什么问题我可以帮助你吗?
query: 浙江的省会在哪?
response: 浙江省的省会是杭州市。
query: 这有什么好吃的
response: 浙江有很多美食,以下列举一些具有代表性的:
1. 杭州菜:杭州作为浙江的省会,以其精致细腻、注重原汁原味而闻名,如西湖醋鱼、龙井虾仁、叫化童鸡等都是特色菜品。
2. 宁波汤圆:宁波的汤圆皮薄馅大,甜而不腻,尤其是冬至和元宵节时,当地人会吃宁波汤圆庆祝。
3. 温州鱼丸:温州鱼丸选用新鲜鱼类制作,口感弹滑,味道鲜美,常常配以海鲜煮食。
4. 嘉兴粽子:嘉兴粽子以其独特的三角形和咸甜两种口味著名,特别是五芳斋的粽子非常有名。
5. 金华火腿:金华火腿是中国著名的腌制肉类,肉质紧实,香味浓郁,常作为节日礼品。
6. 衢州烂柯山豆腐干:衢州豆腐干质地细腻,味道鲜美,是浙江的传统小吃。
7. 舟山海鲜:浙江沿海地带的舟山有丰富的海鲜资源,如梭子蟹、带鱼、乌贼等,新鲜美味。
以上只是部分浙江美食,浙江各地还有许多特色小吃,你可以根据自己的口味去尝试。
history: [('浙江的省会在哪?', '浙江省的省会是杭州市。'), ('这有什么好吃的', '浙江有很多美食,以下列举一些具有代表性的:\n\n1. 杭州菜:杭州作为浙江的省会,以其精致细腻、注重原汁原味而闻名,如西湖醋鱼、龙井虾仁、叫化童鸡等都是特色菜品。\n\n2. 宁波汤圆:宁波的汤圆皮薄馅大,甜而不腻,尤其是冬至和元宵节时,当地人会吃宁波汤圆庆祝。\n\n3. 温州鱼丸:温州鱼丸选用新鲜鱼类制作,口感弹滑,味道鲜美,常常配以海鲜煮食。\n\n4. 嘉兴粽子:嘉兴粽子以其独特的三角形和咸甜两种口味著名,特别是五芳斋的粽子非常有名。\n\n5. 金华火腿:金华火腿是中国著名的腌制肉类,肉质紧实,香味浓郁,常作为节日礼品。\n\n6. 衢州烂柯山豆腐干:衢州豆腐干质地细腻,味道鲜美,是浙江的传统小吃。\n\n7. 舟山海鲜:浙江沿海地带的舟山有丰富的海鲜资源,如梭子蟹、带鱼、乌贼等,新鲜美味。\n\n以上只是部分浙江美食,浙江各地还有许多特色小吃,你可以根据自己的口味去尝试。')]
"""
自我认知微调
接下来我们对模型进行自我认知微调, 官网给的代码是 我们想让模型认为自己是"小黄"而不是"通义千问"; 由"魔搭"训练, 而不是"阿里云". author和name可以自定义修改。
# Experimental environment: A800 * 2
# 80GB GPU memory
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
from swift.llm import DatasetName, ModelType, SftArguments, sft_main
sft_args = SftArguments(
model_type=ModelType.qwen1half_7b_chat,
dataset=[f'{DatasetName.alpaca_zh}#500', f'{DatasetName.alpaca_en}#500',
f'{DatasetName.self_cognition}#500'],
logging_steps=5,
max_length=2048,
learning_rate=1e-4,
output_dir='output',
lora_target_modules=['ALL'],
model_name=['小黄', 'Xiao Huang'],
model_author=['魔搭', 'ModelScope'])
output = sft_main(sft_args)
best_model_checkpoint = output['best_model_checkpoint']
print(f'best_model_checkpoint: {best_model_checkpoint}')
模型量化
# 14GB GPU memory
CUDA_VISIBLE_DEVICES=0 swift export \
--ckpt_dir [微调后保存的模型checkpoints路径] \
--quant_bits 4 --quant_method awq \
--merge_lora true
量化代码运行后,首先要进行量化数据集的下载。
因为我们指定的是:
–quant_bits 4
–quant_method awq
代表设置量化bits数为4,选择的量化方式为awq,也可以选择gptq。
关于swift、sft等命令参数的解释,可以参考这篇文章
数据集下载完成以后就可以进行量化训练,
大概30分钟左右,就可以完成模型量化。
使用微调和量化后的模型进行推理
# Experimental environment: A800 * 2
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
from swift.llm import (
ModelType, get_vllm_engine, get_default_template_type,
get_template, inference_vllm, inference_stream_vllm
)
import torch
model_type = ModelType.qwen1half_7b_chat
model_id_or_path = 'output/qwen1half-7b-chat/vx-xxx/checkpoint-xxx-merged-awq-int4' #使用量化后的模型checkpoint保存路径
llm_engine = get_vllm_engine(model_type,
model_id_or_path=model_id_or_path,
max_model_len=4096)
template_type = get_default_template_type(model_type)
template = get_template(template_type, llm_engine.hf_tokenizer)
# 与`transformers.GenerationConfig`类似的接口
llm_engine.generation_config.max_new_tokens = 512
request_list = [{'query': '你是谁?'}, {'query': '浙江的省会在哪?'}]
resp_list = inference_vllm(llm_engine, template, request_list)
for request, resp in zip(request_list, resp_list):
print(f"query: {request['query']}")
print(f"response: {resp['response']}")
# 流式
history1 = resp_list[1]['history']
query = '这有什么好吃的'
request_list = [{'query': query, 'history': history1}]
gen = inference_stream_vllm(llm_engine, template, request_list)
print_idx = 0
print(f'query: {query}\nresponse: ', end='')
for resp_list in gen:
request = request_list[0]
resp = resp_list[0]
response = resp['response']
delta = response[print_idx:]
print(delta, end='', flush=True)
print_idx = len(response)
print()
print(f"history: {resp_list[0]['history']}")
"""
query: 你是谁?
response: 我是魔搭的人工智能助手,我的名字叫小黄。我可以回答各种问题,提供信息和帮助。有什么我可以帮助你的吗?
query: 浙江的省会在哪?
response: 浙江省的省会是杭州市。
query: 这有什么好吃的
response: 浙江省的美食非常丰富,其中最著名的有杭州的西湖醋鱼、东坡肉、龙井虾仁等。此外,浙江还有许多其他美食,如宁波的汤圆、绍兴的臭豆腐、嘉兴的粽子等。
history: [('浙江的省会在哪?', '浙江省的省会是杭州市。'), ('这有什么好吃的', '浙江省的美食非常丰富,其中最著名的有杭州的西湖醋鱼、东坡肉、龙井虾仁等。此外,浙江还有许多其他美食,如宁波的汤圆、绍兴的臭豆腐、嘉兴的粽子等。')]
"""
对量化模型进行部署
最后一步当然就是部署我们量化后的模型,变成可以通过api访问的形式。
这里是官网给出的部署模型API的代码,我们需要把–ckpt_dir的参数换成我们微调后的模型权重文件,默认的保存路径在这里(/output/v-x xxxxxxx/checkpoint-xxxx-xxx):
# Experimental environment: A100
CUDA_VISIBLE_DEVICES=0 swift deploy \
--ckpt_dir output/qwen1half-72b-chat/vx-xxx/checkpoint-xxx-merged-awq-int4 \
--infer_backend vllm --max_model_len 8192
当然,部署成功以后就会显现这个界面:
然后我们另外开一个.py文件,模拟访问该接口的客户端,我的文件名是modelscope_Qwen1half7B_client.py,代码内容:
from openai import OpenAI
# 初始化OpenAI客户端
client = OpenAI(
api_key='EMPTY',
base_url='http://localhost:8000/v1',
)
# 获取模型类型
model_type = client.models.list().data[0].id
print(f'model_type: {model_type}')
# 初始化聊天记录
messages = []
def chat(query):
# 将用户输入添加到聊天历史记录
messages.append({'role': 'user', 'content': query})
# 调用OpenAI API生成回复
resp = client.chat.completions.create(
model=model_type,
messages=messages,
seed=42
)
# 获取生成的回复
response = resp.choices[0].message.content
print(f'User: {query}')
print(f'Assistant: {response}')
# 将助手回复添加到聊天历史记录
messages.append({'role': 'assistant', 'content': response})
# 实时对话流程
while True:
user_input = input("你: ")
if user_input.lower() in ['退出', 'exit', 'quit']:
break
chat(user_input)
# 流式聊天处理(适用于长文本或连续对话)
def stream_chat(query):
messages.append({'role': 'user', 'content': query})
stream_resp = client.chat.completions.create(
model=model_type,
messages=messages,
stream=True,
seed=42
)
print(f'User: {query}')
print('Assistant: ', end='')
response = ''
# 实时输出流式生成的回复
for chunk in stream_resp:
content_chunk = chunk.choices[0].delta.content
response += content_chunk
print(content_chunk, end='', flush=True)
print()
messages.append({'role': 'assistant', 'content': response})
# 退出程序时的提示
print("聊天结束")
运行成功以后,我们就可以输入我们的对话内容了。
至此,全流程结束。
我们下一个章节将体验如何构建自己的RAG助手。
bug&解决(Reason)
报错1:在通过vLLM进行模型的加速推理时,遇到了报错:
AttributeError: ‘_OpNamespace’ ‘_C’ object has no attribute ‘rms_norm’
主要原因就是:当前vllm版本和现在的torch+cuda不匹配。
我通过卸载当前的vLLM=0.5.2,然后重新安装vLLM=0.5.3,问题解决。
pip uninstall vLLM
pip install vLLM==0.5.3 -i https://pypi.tuna.tsinghua.edu.cn/simple
报错2:TypeError: FormatCode() got an unexpected keyword argument ‘verify‘
主要原因:当前安装的yapf版本不对,版本为0.40.2的yapf源码确实没有该参数。
解决办法:降级yapf
pip install yapf==0.40.0
经验总结(Conclusion)
可以使用pip check和pip show检查冲突和查看包版本
pip check
如果没有问题,则会显示:
No broken requirements found.