vllm加速(以Qwen2.5-7B-instruction为例)与流式响应
1. vllm介绍
-
什么是vllm?
vLLM 是一个高性能的大型语言模型推理引擎,采用创新的内存管理和执行架构,显著提升了大模型推理的速度和效率。它支持高度并发的请求处理,能够同时服务数千名用户,并且兼容多种深度学习框架,方便集成到现有的机器学习流程中。通过一个名为PagedAttention的新型注意力算法来解决传统LLM在生产环境中部署时所遇到的高内存消耗和计算成本的挑战。PagedAttention算法能有效管理注意力机制中的键和值,将它们分割成更小、更易于管理的块,从而减少了vLLM的内存占用,并使其吞吐量超过传统LLM服务方法。
-
为什么需要vllm:
1) 推理加速
2) 模型多并发推理
LLM是同步的,无法进行多并发推理的。正常情况下,当有多个请求依次短时间内进入LLM时,LLM一次只能处理一个问题,其它请求就会进入堵塞状态,直到该问题处理后才会处理下一个。如果强行使用异步或者线程池让模型处理多个请求,只能导致模型内部报错(这个作者尝试了下,确实会报错)。
具体实现原理:待续…
2. vllm中大模型创建的两种方法:LLM(同步)和AsyncLLMEngine(异步)
在 VLLM 框架中,AsyncLLMEngine 和 LLM 的主要区别在于它们的设计目的和使用方式:
- LLM
同步执行:LLM 是一个同步的语言模型接口,通常用于简单的推理任务。在调用时,用户会等待模型处理完请求,才会返回结果。
易于使用:对于初学者或简单应用,LLM 提供了一个直接且简单的接口,可以快速获取推理结果。
执行llm.generate()函数返回的是文本结果,无法做api的流式输出 - AsyncLLMEngine
异步执行:AsyncLLMEngine 设计用于处理高并发请求,使用异步编程模型,允许同时处理多个请求而不阻塞主线程。
性能优化:适合需要高吞吐量和低延迟的应用场景,如实时聊天机器人或在线服务,能够更有效地利用系统资源。
复杂性:使用 AsyncLLMEngine 可能需要开发者对异步编程有一定的理解,但它能显著提升处理能力。
执行llm.generate()函数返回的是一个生成器,用于流式输出 - 总结
如果你的应用需要处理大量并发请求或对响应时间有严格要求,建议使用 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进行登录,并未有页面进行跳转 2.尝试抓包分析请求包数据 我们可以发现页面中存在xml请求,我们就可以构造我们的xml请求语句来获取想要的数据 3.构造语句 <?xml version"1.0" ?> <!DOCTYPE fo…...
从Windows到Linux:跨平台数据库备份与还原
数据库的备份与还原 目录 引言备份 2.1 备份所有数据库2.2 备份单个数据库2.3 备份多个指定数据库 传输备份文件还原 4.1 还原所有数据库4.2 还原单个数据库4.3 还原多个指定数据库 注意事项拓展 1. 引言 在不同的操作系统间进行数据库迁移时,命令行工具是我们的…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
