当前位置: 首页 > news >正文

vllm加速(以Qwen2.5-7B-instruction为例)与流式响应

1. vllm介绍

  1. 什么是vllm?
    vLLM 是一个高性能的大型语言模型推理引擎,采用创新的内存管理和执行架构,显著提升了大模型推理的速度和效率。它支持高度并发的请求处理,能够同时服务数千名用户,并且兼容多种深度学习框架,方便集成到现有的机器学习流程中。

    通过一个名为PagedAttention的新型注意力算法来解决传统LLM在生产环境中部署时所遇到的高内存消耗和计算成本的挑战。PagedAttention算法能有效管理注意力机制中的键和值,将它们分割成更小、更易于管理的块,从而减少了vLLM的内存占用,并使其吞吐量超过传统LLM服务方法。

  2. 为什么需要vllm:
    1) 推理加速
    2) 模型多并发推理
    LLM是同步的,无法进行多并发推理的。正常情况下,当有多个请求依次短时间内进入LLM时,LLM一次只能处理一个问题,其它请求就会进入堵塞状态,直到该问题处理后才会处理下一个。如果强行使用异步或者线程池让模型处理多个请求,只能导致模型内部报错(这个作者尝试了下,确实会报错)。

具体实现原理:待续…

2. vllm中大模型创建的两种方法:LLM(同步)和AsyncLLMEngine(异步)

在 VLLM 框架中,AsyncLLMEngine 和 LLM 的主要区别在于它们的设计目的和使用方式:

  1. LLM
      同步执行:LLM 是一个同步的语言模型接口,通常用于简单的推理任务。在调用时,用户会等待模型处理完请求,才会返回结果。
      易于使用:对于初学者或简单应用,LLM 提供了一个直接且简单的接口,可以快速获取推理结果。
      执行llm.generate()函数返回的是文本结果,无法做api的流式输出
  2. AsyncLLMEngine
      异步执行:AsyncLLMEngine 设计用于处理高并发请求,使用异步编程模型,允许同时处理多个请求而不阻塞主线程。
      性能优化:适合需要高吞吐量和低延迟的应用场景,如实时聊天机器人或在线服务,能够更有效地利用系统资源。
      复杂性:使用 AsyncLLMEngine 可能需要开发者对异步编程有一定的理解,但它能显著提升处理能力。
      执行llm.generate()函数返回的是一个生成器,用于流式输出
  3. 总结
      如果你的应用需要处理大量并发请求或对响应时间有严格要求,建议使用 AsyncLLMEngine。
      对于简单的任务或低并发场景,LLM 可能更为合适和易于实现。

3. 流式输出、非流失输出和vllm的同步、异步关系

对于vllm同步:无论是流式还是非流式输出,vllm的LLM函数创建的模型对象通常以同步的方式工作,处理多并发情况时只能以队列形式一个个输出。对于非流式输出,它会阻塞直到生成完成并返回结果;对于流式输出,它也可以逐步返回数据给前端,但这是假流式,因为后端以及把所有的文本都输出了,然后我们又把文本一个个传给前端。

对vllm异步:异步引擎同样可以支持流式和非流式输出,但它允许你以非阻塞的方式处理这些输出。你可以启动一个生成任务而不等待它完成,然后根据需要逐步获取流式输出,或者在任务完成后一次性获取非流式输出(也是并发状态)。这为高并发环境下的应用提供了更好的性能和灵活性。

总结:流式输出和非流式输出关注的是输出的传输方式,而AsyncLLMEngine和LLM则更多地涉及到执行模式(同步 vs 异步)。两者可以组合使用,例如,你可以使用AsyncLLMEngine来异步地处理流式输出,从而在高并发环境中获得最佳性能和用户体验。

4. vllm的LLM(同步)

from transformers import AutoTokenizer
from vllm import LLM, SamplingParamstokenizer = AutoTokenizer.from_pretrained("模型地址")
sampling_params = SamplingParams(temperature=0.7, top_p=0.8, repetition_penalty=1, max_tokens=1024)
llm = LLM(model="模型地址")prompt = "请帮我写一篇800字的关于“春天”的作文"
messages = [{"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True
)# text为文本和dict都可,但是对格式有特殊要求。
outputs = llm.generate([text], sampling_params=sampling_params)
# # Print the outputs.
for output in outputs:prompt = output.promptgenerated_text = output.outputs[0].textprint(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

上述代码中对于llm.generate的输入,研究代码底层函数,可以发现llm.generate的输入prompts可以是数组,即多个问题批量处理(批量处理还是一个线程,并不是说llm的批量处理是使用多个线程处理的),也可以是单个元素:dict或者文本
  为dict时,dict必须包含prompt_token_ids的key,value为最终文本的编码。如果不包含prompt_token_ids的属性时,就必须存在prompt这个key(value为未编码的最终文本,即|im_read|这种最终文本格式),用于底层编码为embedding,并交给llm生成答案。所以说如果对llm.generate传入的是向量编码,就必须是最终文本的向量编码,并且放在prompt_token_ids这个key中。
  为str时,则必须是这个大模型的训练时最后交给llm推理答案的格式(注意:上述代码中的text已经apply_chat_template过了,变成|im_read|那种格式了),即|im_read|这种最终文本格式。而代码底层会如果判断为str,就会直接将其视为prompt最终文本,并编码为向量保存在prompt_token_ids中交给llm生成答案。

根据上述延伸一下:当使用vllm加速自己微调过的模型时,微调模型时的训练集最后交给llm进行推理的格式,必须要和vllm得apply_chat_templat之后得格式一样(如果vllm输入为文本而并非编码向量的话),即训练和推理时,模型的输入格式要相同。

还需要注意llm.generate是没有经过包装的chat函数,它接收的输入如果是对话指令则进行对话,如果是文本,则直接进行文本续写。需要要看传入的text是一段文本还是{‘role’: ‘user’, ‘content’:’‘}格式的对话,决定生产的文本类型。如果是chat函数函数,则只能进行对话,且只需要输入问题,而不需要apply_chat_template函数包装。

5. vllm异步的AsyncLLMEngine(异步)

# 后端代码
import os
import json
import uuid
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.responses import StreamingResponse
from transformers import AutoTokenizer
from vllm import AsyncEngineArgs, AsyncLLMEngine, SamplingParams# 响应对象
class ChatCompletionStreamResponse(BaseModel):success: boolcontent: str = ''# 将python对象转化为json对象   
def jsonify(data: "BaseModel") -> str:try:  # pydantic v2return json.dumps(data.model_dump(exclude_unset=True), ensure_ascii=False)except AttributeError:  # pydantic v1return data.json(exclude_unset=True, ensure_ascii=False)def event_serializer(obj):return {'success': obj.success, 'content': obj.content}app = FastAPI()
tokenizer = AutoTokenizer.from_pretrained("/media/houfengzhen/张璐璐/chentao/Qwen2.5-7B-Instruct")
engine_args = {"model": "/media/houfengzhen/张璐璐/chentao/Qwen2.5-7B-Instruct","trust_remote_code": True,"dtype": 'bfloat16',"enforce_eager": True,"tensor_parallel_size": 1,"gpu_memory_utilization": 0.9
}
prompt = "你是谁"
messages = [{"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True
)llm = AsyncLLMEngine.from_engine_args(AsyncEngineArgs(**engine_args))
@app.post("/api")
async def chat_complete():# 注意此处为流式输出,非流式输出直接将生成的内容全接接收完后,直接return即可# 流式输出的返回需要StreamingResponse包装!!!!return StreamingResponse(generate_text(), media_type="text/event-stream")async def generate_text():sampling_params = SamplingParams(repetition_penalty=1.0or 1.0,  # repetition_penalty must > 0temperature=0.7,stop_token_ids=[tokenizer.eos_token_id] + tokenizer.additional_special_tokens_ids,max_tokens=512,skip_special_tokens=True)request_id = "chatcmpl-{}".format(uuid.uuid4().hex)# 注意此处的输入,text和embdding都不能为[],只能是单个元素:str或者{}。# embdding需要为dict,且{}中存在key: prompt_token_ids、prompt# text 则是最终文本即可result_generator = llm.generate(text,sampling_params=sampling_params,request_id=request_id,lora_request=None,)# result_generator = llm.generate(#         {'prompt_token_ids': [151644, 8948, 198, 56568, 101909, 99471, 47764, 104799, 102064, 101057, 151645, 198, 151644, 872, 198, 36667, 52183, 27369, 28311, 6, 29991, 6, 30440, 99250, 105395, 17714, 5122, 675, 198, 6, 18, 24, 8908, 108, 223, 73670, 111138, 102992, 102204, 99661, 11319, 6, 30440, 99250, 105395, 17714, 5122, 18, 24, 13, 10479, 1231, 8683, 438, 264, 25202, 18630, 18239, 320, 43, 934, 86427, 27705, 13, 16, 13, 220, 56007, 36987, 104595, 99556, 28946, 18987, 91282, 69249, 100630, 100165, 11319, 6, 30440, 99250, 105395, 17714, 5122, 35, 13, 16, 13, 1207, 25, 10479, 374, 5230, 304, 279, 7271, 315, 1036, 90799, 48481, 854, 5267, 27705, 13, 20, 13, 220, 56007, 5122, 100165, 114532, 110124, 2073, 99204, 113484, 9370, 101939, 18987, 11319, 6, 30440, 99250, 105395, 17714, 5122, 35, 13, 20, 10003, 25, 10479, 374, 6509, 264, 1036, 37555, 1682, 854, 1939, 100345, 104120, 36667, 52183, 27369, 37945, 44063, 6, 105043, 100165, 6, 105395, 17714, 105205, 3837, 101097, 66017, 105395, 104813, 109949, 3837, 110263, 42855, 30868, 101090, 17177, 1773, 151645, 198, 151644, 77091, 198],#          'prompt':None},#         sampling_params=sampling_params,#         request_id=request_id,#         lora_request=None,#     )generated_text = ""async for result in result_generator:generated_text = result.outputs[0].textprint(generated_text)# 流式输出特殊处理'''为什么要加 '\n'?此处需要使用json.dumps函数将每一次的Python对象转化为json字符串当通过HTTP或WebSocket等协议发送流式数据时,客户端需要一种方式来区分接收到的不同数据块。换行符 \n 可以作为一个简单的				 	分隔符,使得每一行代表一个独立的数据包。这有助于前端解析器更容易地区分和处理每个单独的消息,而不会将多个消息混淆为一个.例如,如果你不使用任何分隔符,那么所有发送的JSON对象可能会被合并成一个连续的字符串,导致前端难以正确解析这些数据。假设你发送了两个JSON对象:{"success": true, "content": "Hello"}{"success": true, "content": " World"}没有分隔符的情况下,它们会被视为一个无效的JSON字符串,因为JSON格式不允许两个对象直接相连。但是,如果我们在每个JSON对象后面加上换行符:{"success": true, "content": "Hello"}\n{"success": true, "content": " World"}\n这样,前端可以轻松地按行读取(r.iter_lines())并解析每个JSON对象,确保每个消息都被正确处理''''''# 1. 方式一# chunk = ChatCompletionStreamResponse(success=True, content=generated_text)# yield jsonify(chunk) + '\n'# 2.方式二# 这个 event_serializer 函数的作用是处理那些默认情况下无法被 JSON 序列化的对象,比如自定义类实例、日期时间对象、集合等。在此处是将其转化为dict对象,再交给json.dumps处理# chunk = ChatCompletionStreamResponse(success=True, content=generated_text)# yield json.dumps(chunk, default=event_serializer) + '\n'# 3.方式三yield json.dumps({'success': True, 'content': generated_text}) + '\n'if __name__ == "__main__":uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("API_PORT", 7777)), workers=1)
import json
import requestsurl_api = "http://localhost:7777/api"# 这种请求更安全,另一种请求请见 ‘fastAPI接口的请求与响应——基础:https://editor.csdn.net/md/?articleId=144560081’一文
with requests.post(url_api, stream=True) as r:r.raise_for_status()  # 检查请求是否成功for line in r.iter_lines():if line:  # 过滤掉保持连接的空行print(json.loads(line.decode('utf-8')))

上述代码模型可以处理多个请求,且流式响应。如果不想流式响应,直接将流式输出的内容拼接,一次性返回即可。

6. 注意:

在发送请求后,接收流式响应前,如果执行力print(response.content),可能会导致流式输出变成假流式输出:即等待后端全部输出完成后,前端才开始输出。

这是因为 response.content 属性会强制 requests 库读取整个HTTP响应体,并将其作为一个字节字符串返回。当你访问这个属性时,实际上是在告诉 requests 立刻获取所有的响应数据,这与流式处理的概念相悖。流式处理的目的是允许你逐步处理接收到的数据,而不是一次性读取所有内容。因此,当你使用 stream=True 参数进行请求时,你不应该使用 response.content 或 response.text,因为它们都会触发对整个响应体的读取。相反,你应该使用 response.iter_content() 或 response.iter_lines() 来逐块或逐行处理响应。

相关文章:

vllm加速(以Qwen2.5-7B-instruction为例)与流式响应

1. vllm介绍 什么是vllm? vLLM 是一个高性能的大型语言模型推理引擎,采用创新的内存管理和执行架构,显著提升了大模型推理的速度和效率。它支持高度并发的请求处理,能够同时服务数千名用户,并且兼容多种深度学习框架,…...

WordPress弹窗公告插件-ts小陈

使用效果 使用后网站所有页面弹出窗口 插件特色功能 设置弹窗公告样式:这款插件可展示弹窗样式公告,用户点击完之后不再弹出,不会频繁打扰用户。可设置弹窗中间的logo图:这款插件针对公告图片进行独立设置,你可以在设…...

【ELK】容器化部署Elasticsearch1.14.3集群【亲测可用】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1. 部署1.1 单节点1.2 新节点加入集群1.3 docker-compose部署集群 1. 部署 按照官网流程进行部署 使用 Docker 安装 Elasticsearch |Elasticsearch 指南 [8.14] |…...

[SAP ABAP] ALV状态栏GUI STATUS的快速创建

使用事务码SE38进入到指定程序,点击"显示对象列表"按钮 鼠标右键,选择"GUI状态" 弹出【创建状态】窗口,填写状态以及短文本描述以后,点击按钮 点击"调整模板",复制已有程序的状态栏 填…...

【Linux】NET9运行时移植到低版本GLIBC的Linux纯内核板卡上

背景介绍 自制了一块Linux板卡(基于全志T113i) 厂家给的SDK和根文件系统能够提供的GLIBC的版本比较低 V2.25/GCC 7.3.1 这个版本是无法运行dotnet以及dotnet生成的AOT应用的 我用另一块同Cortex-A7的板子运行dotnet的报错 版本不够,运行不了 而我的板子是根本就识…...

深入浅出支持向量机(SVM)

1. 引言 支持向量机(SVM, Support Vector Machine)是一种常见的监督学习算法,广泛应用于分类、回归和异常检测等任务。自1990年代初期由Vapnik等人提出以来,SVM已成为机器学习领域的核心方法之一,尤其在模式识别、文本…...

Vue脚手架相关记录

脚手架 安装与配置 安装node node -> 16.20.2 切换淘宝镜像 npm install -g cnpm -registryhttp://registry.npm.taobao.orgnpm config set registry http://registry.npm.taobao.org/使用了第二个,下一步才有用 安装vue npm install -g vue/clivscode中不给运行vue解…...

基于Docker的Minio分布式集群实践

目录 1. 说明 2. 配置表 3. 步骤 3.1 放行服务端口 3.2 docker-compose 编排 4. 入口反向代理与负载均衡配置 4.1 api入口 4.2 管理入口 5. 用例 6. 参考 1. 说明 以多节点的Docker容器方式实现minio存储集群,并配以nginx反向代理及负载均衡作为访问入口。…...

Scala 的迭代器

迭代器定义:迭代器不是一种集合,它是一种用于访问集合的方法。 迭代器需要通过集合对应的迭代器调用迭代器的方法来访问。 支持函数式编程风格,便于链式操作。 创建一个迭代器,相关代码如下: object Test {def mai…...

vue实现文件流形式的导出下载

文章目录 Vue 项目中下载返回的文件流操作步骤一、使用 Axios 请求文件流数据二、设置响应类型为 ‘blob’三、创建下载链接并触发下载四、在 Vue 组件中集成下载功能五、解释与实例说明1、使用 Axios 请求文件流数据:设置响应类型为 blob:创建下载链接并…...

【DIY飞控板PX4移植】深入理解NuttX下PX4串口配置:ttyS设备编号与USARTUART对应关系解析

深入理解NuttX下PX4串口配置:ttyS设备编号与USART&UART对应关系解析 引言问题描述原因分析结论 引言 在嵌入式系统开发中,串口(USART/UART)的配置是一个常见但关键的任务。对于使用 NuttX 作为底层操作系统的飞控系统&#x…...

【报错解决】vsvars32.bat 不是内部或外部命令,也不是可运行的程序或批处理文件

报错信息: 背景问题:Boost提示 “cl” 不是内部或外部命令,也不是可运行的程序或批处理文件时,   按照这篇博客的方法【传送】添加了环境变量后,仍然报错: 报错原因: vsvars32.bat 的路径不正…...

CTFshow-文件上传(Web151-170)

CTFshow-文件上传(Web151-170) 参考了CTF show 文件上传篇(web151-170,看这一篇就够啦)-CSDN博客 Web151 要求png,然后上传带有一句话木马的a.png,burp抓包后改后缀为a.php,然后蚁剑连接,找fl…...

深度学习基础--将yolov5的backbone模块用于目标识别会出现怎么效果呢??

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 前言 yolov5网络结构比较复杂,上次我们简要介绍了yolov5网络模块,并且复现了C3模块,深度学习基础–yolov5网络结构简介&a…...

操作系统(16)I/O软件

前言 操作系统I/O软件是负责管理和控制计算机系统与外围设备(例如键盘、鼠标、打印机、存储设备等)之间交互的软件。 一、I/O软件的定义与功能 定义:I/O软件,也称为输入/输出软件,是计算机系统中用于管理和控制设备与主…...

leetcode437.路径总和III

标签:前缀和 问题:给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下…...

WebGPU、WebGL 和 OpenGL/Vulkan对比分析

WebGPU、WebGL 和 OpenGL/Vulkan 都是用于图形渲染和计算的图形API,但它们的设计理念、功能和适用场景有所不同。以下是它们的总结和对比分析: 1. WebGPU WebGPU 是一个新的、现代化的图形和计算API,设计目的是为Web平台提供更接近硬件的性…...

不可重入锁与死锁

不可重入锁确实可能导致死锁,特别是在同一线程尝试多次获取同一把锁时。如果锁是不可重入的,那么线程在第二次尝试获取锁时会永远阻塞,从而导致死锁。 不可重入锁与死锁的关系 不可重入锁不允许同一个线程多次获取同一把锁。在以下情况下&am…...

XXE-Lab靶场漏洞复现

1.尝试登录 输入账号admin/密码admin进行登录&#xff0c;并未有页面进行跳转 2.尝试抓包分析请求包数据 我们可以发现页面中存在xml请求&#xff0c;我们就可以构造我们的xml请求语句来获取想要的数据 3.构造语句 <?xml version"1.0" ?> <!DOCTYPE fo…...

从Windows到Linux:跨平台数据库备份与还原

数据库的备份与还原 目录 引言备份 2.1 备份所有数据库2.2 备份单个数据库2.3 备份多个指定数据库 传输备份文件还原 4.1 还原所有数据库4.2 还原单个数据库4.3 还原多个指定数据库 注意事项拓展 1. 引言 在不同的操作系统间进行数据库迁移时&#xff0c;命令行工具是我们的…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...