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. 引言 在不同的操作系统间进行数据库迁移时,命令行工具是我们的…...

upload-labs
Win平台靶场 靶场2 教程 教程 教程 pass-01 bash 本pass在客户端使用js对不合法图片进行检查!前端绕过, 禁用前端js代码, 或者上传图片, 抓包改后缀为 php , 后端没有校验 bash POST /Pass-01/index.php HTTP/1.1 Host: 47.122.3.214:8889 Content-Length: 49…...

【西门子PLC.博途】——面向对象编程及输入输出映射FC块
当我们做面向对象编程的时候,需要用到输入输出的映射。这样建立的变量就能够被复用,从而最大化利用了我们建立的udt对象。 下面就来讲讲映射是什么。 从本质上来说,映射就是拿实际物理对象对应程序虚拟对象,假设程序对象是I0.0&…...

牛客周赛 Round 72 题解
本次牛客最后一个线段树之前我也没碰到过,等后续复习到线段树再把那个题当例题发出来 小红的01串(一) 思路:正常模拟,从前往后遍历一遍去统计即可 #include<bits/stdc.h> using namespace std; #define int lo…...

Flux Tools 结构简析
Flux Tools 结构简析 BFL 这次一共发布了 Canny、Depth、Redux、Fill 四个 Tools 模型系列,分别对应我们熟悉的 ControlNets、Image Variation(IP Adapter)和 Inpainting 三种图片条件控制方法。虽然实现功能是相同的,但是其具体…...

0 前言
ArCS作为一个基于Rust的CAD(计算机辅助设计)开源系统,尽管已经有四年未更新,但其设计理念和技术实现仍然具有很高的学习和参考价值。以下是对ArCS项目的进一步分析和解读: 一、项目亮点与技术优势 高效与安全的Rust语…...

ARM嵌入式学习--第八天(PWM)
PWM -PWM介绍 PWM(pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在测量,通信,工控等方面 PWM的频率 是指在1秒钟内,信号从…...

遇到“REMOTE HOST IDENTIFICATION HAS CHANGED!”(远程主机识别已更改)的警告
连接虚拟机时提示报错: [insocoperhq-soc-cap-raw3 ~]$ ssh root10.99.141.104WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-midd…...

vue3前端组件库的搭建与发布(一)
前言: 最近在做公司项目中,有这么一件事情,很是头疼,就是同一套代码,不同项目,要改相同bug,改好多遍,改的都想吐,于是就想做一个组件库,这样更新一下就全都可…...

COMSOL快捷键及内置函数
文章目录 COMSOL快捷键使用COMSOL算子求最大值和最小值COMSOL内置函数3.1 解析函数3.2 插值函数3.3 分段函数3.4 高斯脉冲函数3.5 斜坡函数3.6 矩形函数3.7 波形函数3.8 随机函数3.9 Matlab函数3.10 SWITCH函数 COMSOL快捷键 Ctrl+/ 可快速打开预定义的物理量列表。…...

HUAWEI-eNSP交换机链路聚合(手动负载分担模式)
配置思路:HUAWEI交换机链路聚合有LACP模式跟手动负载分担模式,本文主打手动负载分担模式:首先交换机-PC之间划分基本vlan,交换机-交换机之间创建链路聚合组,划分端口至链路聚合分组(缺省模式为手动负载分担模式)。结果验证要求同vlan可以ping通,关闭某个聚合端口后仍可…...