最近在《AI 大模型全栈工程师》课程里看老师推荐使用vllm部署大模型,优点就不详细介绍了,这里摘抄一段来自于Qwen2上手指南对于它的简单介绍:它易于使用,且具有最先进的服务吞吐量、高效的注意力键值内存管理(通过PagedAttention实现)、连续批处理输入请求、优化的CUDA内核等功能。
至于原理就先不看了,直接上手部署,以后再来补理论知识。
在Qwen2的上市指南里介绍了vllm的离线推理方式,但由于笔者参与的一个项目是基于RAG技术搭建的知识库系统,提供用户在线问答的方式,因此,需要改造成vllm的在线推理方式,于是试图对原有使用transformer实现chat流式输出的方式进行改造。
这是笔者找到的参考项目代码GitHub - CadenCao/vllm-qwen1.5-StreamChat: 用VLLM框架部署千问1.5并进行流式输出,并下载了vllm的源代码vllm/vllm at main · vllm-project/vllm · GitHub。
在项目vllm-qwen1.5-StreamChat的server_vllm.py中有对流式输出的代码实现,但是笔者发现这里调用的函数跟vllm项目源代码提供的函数参数并不相同。(这算是第一个坑)
# server_vllm.py第139行
generater = engine.generate(prompt=None,
sampling_params=sampling_params,
prompt_token_ids=prompt_tokens,
request_id=request_id)
# async_llm_engine.py第622行
async def generate(
self,
inputs: PromptInputs,
sampling_params: SamplingParams,
request_id: str,
lora_request: Optional[LoRARequest] = None,
trace_headers: Optional[Dict[str, str]] = None,
)
笔者随后就按照async_llm_engine.py里提供的api来传参inputs、sampling_params和request_id,好不容易确实流式输出了问答内容,但只是对于单个prompt的输出,如果想要支持多轮对话,对于PromptInputs应该怎么传参,一直没摸索出来。
这里还有另外一个坑,由于vllm无法在windows环境下安装,只能运行在linux环境下或者使用wsl,笔者就是在wsl下来运行代码的,但由于无法使用ide,调试只能纯手工,严重影响开发效率。(这算是第二个坑)
两天折腾下来,笔者没了耐心,干脆换了个思路,为什么Qwen不提供在线推理的demo代码,而提供的是“适配OpenAI-API的API服务”?
笔者猜测,是不是Qwen官方推荐的在线推理方式就是直接用适配OpenAI-API的API服务方式呢?
笔者随后就改在wsl中直接部署大模型Qwen2.0(cuda版本是12.3),部署及运行相关的脚本代码如下:
pip install vLLM>=0.5.0
# /opt/models/Qwen2-7B-Instruct是模型路径地址
python -m vllm.entrypoints.openai.api_server --model /opt/models/Qwen2-7B-Instruct
如果需要运行多个gpu,也可以增加传参tensor-parallel-size,譬如如下命令:
python -m vllm.entrypoints.api_server \
--model /opt/models/Qwen2-7B-Instruct \
--tensor-parallel-size 4
然后,笔者在python服务端加入流式传输的参数及处理逻辑demo代码如下:
from openai import OpenAI
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
chat_response = client.chat.completions.create(
model="/opt/models/Qwen2-7B-Instruct",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me something about large language models."},
],
stream=True
)
for chat in chat_response:
content = chat.choices[0].delta.content
if content:
print("Chat response:", content)
笔者发现,通过使用vllm把大模型部署成api服务后,调用大模型对话太简单易用了,再也不想回到原来的原生调用方式。另外,如果后期更换大模型,由于模型对话调用方法存在差异,经常需要另外再写一套代码来适配,如果使用vllm部署成标准服务的方式,那也不存在这个问题了。
由于公司配备装有4090D显卡的电脑有限,以前只能在装有这个显卡的机器上调试代码,在部署成服务后,模型代码和应用代码分离,可以将api服务在内网中开放出来,专注于应用开发的同事,可以使用低配的电脑就可以进行开发工作,提高工作效率。
以下是将wsl的端口开放出来的脚本命令。
netsh interface portproxy add v4tov4 listenaddress=* listenport=8000 connectaddress=IP connectport=8000 protocol=tcp # IP修改为WSL的IP地址,增加监听8000端口
另外,再把windows的8000端口在defender防火墙上开放访问即可。
vllm服务部署及对外开放访问的任务完成后,找时间又研究了一下vllm的源码。
vllm服务的调用方法client.chat.completions.create将请求发送到文件api_server.py的方法create_chat_completion中,通过一系列对传参的转换后,包括将messages转换为prompt,prompt再转换成prompt_ids,两者再构成PromptInputs类型的参数inputs,最终再调用第一节中提到的generate方法。
整个逻辑还是很复杂的,并且vllm内部已经提供实现,所以,笔者真切觉得实在没必要自己来手写这部分代码。
此部分为新增内容。
有网友私信碰到OOM(内存溢出)问题该怎么处理?Qwen2的官方文档其实给了说明如下:
您可能会遇到令人烦恼的OOM(内存溢出)问题。我们推荐您尝试两个参数进行修复。第一个参数是
--max-model-len
。我们提供的默认最大位置嵌入(max_position_embedding)为32768,因此服务时的最大长度也是这个值,这会导致更高的内存需求。将此值适当减小通常有助于解决OOM问题。另一个您可以关注的参数是--gpu-memory-utilization
。默认情况下,该值为0.9
,您可以将其调高以应对OOM问题。这也是为什么您发现一个大型语言模型服务总是占用大量内存的原因。
笔者也遇到了此问题,并采用了第二种方法,通过加大gpu内存利用率来处理的。命令参考如下:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2-7B-Instruct \
--gpu-memory-utilization 0.95