【vllm]缓冲区复用01
缓冲区、原始数据与 Copy 原理详解一、什么是缓冲区缓冲区 一块连续的内存区域 用来临时存储数据 内存地址: 0x1000 0x1001 0x1002 0x1003 0x1004 内存内容: [ 72 ] [ 101] [ 108] [ 108] [ 111] H e l l o buffer bytearray(bHello) └── 就是这样一块内存 └── bytearray 是可变的字节数组 └── Python 管理这块内存的生命周期二、原始数据到缓冲区的过程原始数据Python 对象# EngineCoreOutputs 是一个Python对象outputsEngineCoreOutputs(outputs[RequestOutput(request_idreq1,token_ids[1234,5678],finishedFalse,)],scheduler_statsSchedulerStats(num_running_reqs3,num_waiting_reqs1,))# 这个对象在内存中是分散的:# outputs 对象头 → 地址 0x2000# outputs列表 → 地址 0x3000# RequestOutput对象 → 地址 0x4000# token_ids列表 → 地址 0x5000# scheduler_stats → 地址 0x6000# 各个字段分散在堆内存各处Python对象在内存中的样子: 堆内存: ┌─────────────────────────────────────────────────┐ │ 0x2000: EngineCoreOutputs │ │ ├── outputs_ptr ──────────────────────►0x3000 │ │ └── stats_ptr ───────────────────────►0x6000 │ │ │ │ 0x3000: list │ │ └── item[0]_ptr ─────────────────────►0x4000 │ │ │ │ 0x4000: RequestOutput │ │ ├── request_id req1 │ │ ├── token_ids_ptr ───────────────────►0x5000 │ │ └── finished False │ │ │ │ 0x5000: list [1234, 5678] │ │ │ │ 0x6000: SchedulerStats │ │ ├── num_running 3 │ │ └── num_waiting 1 │ └─────────────────────────────────────────────────┘ 问题: 网络传输需要连续的字节流 不能直接传输这种分散的Python对象 需要序列化成连续的字节序列化对象 → 连续字节encoderMsgpackEncoder()bufferbytearray()buffersencoder.encode_into(outputs,buffer)序列化过程: Python对象 (分散内存) buffer (连续内存) ┌──────────────────┐ ┌──────────────────────────────┐ │ 0x2000: outputs │ │ 0x7000: │ │ 0x3000: list │ encode │ [82][a7]outputs[91][...req1 │ │ 0x4000: ReqOut │ ───────► │ ...][1234][5678][a5]stats[03]│ │ 0x5000: tokens │ │ [01] │ │ 0x6000: stats │ │ ← 连续的msgpack字节流 → │ └──────────────────┘ └──────────────────────────────┘ 序列化后: └── 所有数据打包成连续字节 └── 可以直接通过网络传输 └── 接收方可以反序列化还原Python对象三、copyTrue 的原理coord_socket.send(coord_buf,copyTrue)copyTrue 的内存操作: Step1: Python 持有 buffer ┌─────────────────────────────────┐ │ Python内存 (0x7000): │ │ [82][a7][o][u][t][p][u][t][s] │ │ [91][...][1234][5678][...] │ └─────────────────────────────────┘ │ │ copyTrue ▼ ZMQ 复制一份到自己的内存 ┌─────────────────────────────────┐ │ ZMQ内存 (0x8000): │ │ [82][a7][o][u][t][p][u][t][s] │ ← 完整拷贝 │ [91][...][1234][5678][...] │ └─────────────────────────────────┘ Step2: send() 返回后 ├── Python 的 buffer 可以立即释放/复用 ├── ZMQ 用自己的副本发送数据 └── 两块内存互不影响 Step3: ZMQ 后台线程发送 ZMQ内存 (0x8000) ──网络──► API Server Step4: 发送完成 ZMQ内存 (0x8000) 释放copyTrue 优缺点: 优点: ├── 简单安全 ├── send()返回后立即可以复用buffer └── 不需要追踪发送状态 缺点: └── 额外的内存分配和数据拷贝 数据量大时影响性能 例如: 1MB的输出数据 → Python buffer 1MB → ZMQ buffer 1MB (拷贝) → 总共 2MB 内存四、copyFalse 的原理零拷贝trackersockets[client_index].send_multipart(buffers,copyFalse,trackTrue,)copyFalse 的内存操作: Step1: Python 持有 buffer ┌─────────────────────────────────┐ │ Python内存 (0x7000): │ │ [82][a7][o][u][t][p][u][t][s] │ │ [91][...][1234][5678][...] │ └─────────────────────────────────┘ Step2: send(copyFalse) 调用 ZMQ 不复制数据 只记录 Python buffer 的地址: ┌─────────────────────────────────┐ │ ZMQ 内部记录: │ │ data_ptr 0x7000 ← 指向Python │ │ data_len 1024 │ │ 直接用这个地址发送 │ └─────────────────────────────────┘ Step3: ZMQ 后台线程发送中 Python内存 (0x7000) ──网络──► API Server ↑ ★ 此时绝对不能释放或修改这块内存! ZMQ 还在读取它 Step4: 发送完成 tracker.done True └── 此刻才能安全释放/复用 Python buffercopyFalse 优缺点: 优点: ├── 零内存拷贝 ├── 节省内存 └── 提高吞吐量 例如: 1MB的输出数据 → Python buffer 1MB → ZMQ 直接用这1MB → 总共 1MB 内存 缺点: ├── 必须追踪发送状态 ├── buffer 不能提前释放 └── 代码更复杂五、tracker 和 pending 的作用trackersocket.send_multipart(buffers,copyFalse,trackTrue)ifnottracker.done:pending.appendleft((tracker,outputs,buffer))eliflen(reuse_buffers)max_reuse_bufs:reuse_buffers.append(buffer)tracker 的状态变化: T0ms: send_multipart() 调用 tracker.done False ← 发送中 buffer 不能动! pending [(tracker, outputs, buffer)] T1ms: ZMQ后台线程读取 buffer 数据 通过网络发送 tracker.done False ← 还在发 T5ms: 网络发送完成 tracker.done True ← 发完了! buffer 现在可以安全复用 下一轮循环: while pending and pending[-1][0].done: _, _, buf pending.pop() reuse_buffers.append(buf) ← 放回缓冲池pending deque 的结构: appendleft 从左边加入 (最新的在左) pop 从右边取出 (最旧的在右) 时间轴: T1: send buf0 pending [buf0] T2: send buf1 pending [buf1, buf0] T3: send buf2 pending [buf2, buf1, buf0] T4: buf0发完 pending[-1] buf0, doneTrue pending.pop() → 回收buf0 pending [buf2, buf1] T5: buf1发完 pending[-1] buf1, doneTrue pending.pop() → 回收buf1 pending [buf2]六、缓冲区复用池原理reuse_buffers:list[bytearray][]max_reuse_bufs4# 取出复用:bufferreuse_buffers.pop()ifreuse_bufferselsebytearray()# 放回复用:iflen(reuse_buffers)max_reuse_bufs:reuse_buffers.append(buffer)没有复用池时: 每次循环: T1: buffer bytearray() ← malloc 分配内存 encode_into(outputs, buffer) send(buffer) 发完后 buffer 被GC回收 ← free 释放内存 T2: buffer bytearray() ← malloc 再次分配 ... 问题: └── 频繁 malloc/free └── 内存碎片 └── GC压力大 └── 高吞吐时性能下降 有复用池时: 初始: reuse_buffers [] T1: buffer bytearray() ← 第一次才malloc encode_into(outputs, buffer) 发完后: reuse_buffers [buffer] ← 不释放留着 T2: buffer reuse_buffers.pop() ← 直接复用! encode_into(outputs, buffer) ← 覆盖写入新数据 发完后: reuse_buffers [buffer] T3: buffer reuse_buffers.pop() ← 再次复用! ... 优势: └── 只有第一次 malloc └── 之后一直复用同一块内存 └── 无GC压力 └── 高吞吐时性能稳定encode_into 覆盖写入: 第一次: buffer bytearray() (空的, 长度0) encode_into(outputs1, buffer) buffer bytearray([82, a7, ...]) (长度1024) 第二次(复用): buffer bytearray([82, a7, ...]) (上次的数据) encode_into(outputs2, buffer) → 清空buffer → 重新写入新数据 buffer bytearray([91, b3, ...]) (新数据, 覆盖了旧的)七、完整内存生命周期一次输出的完整内存流转: 主线程: outputs EngineCoreOutputs(...) ← Python对象创建 output_queue.put((client_idx, outputs)) 输出线程: output output_queue.get() ① 获取缓冲区: buffer reuse_buffers.pop() ← 复用 或 新建bytearray ② 序列化: encode_into(outputs, buffer) 此时: Python对象(分散) ──序列化──► buffer(连续字节) ③ 零拷贝发送: tracker socket.send_multipart(buffer, copyFalse) 此时: buffer内存 ──ZMQ直接引用──► 网络发送中 ↑ 不能动! ④ 追踪: if not tracker.done: pending.appendleft((tracker, outputs, buffer)) ↑ 保持buffer的引用防止被GC ⑤ 发送完成(下一轮检查): tracker.done True pending.pop() ← 移除追踪 reuse_buffers.append(buffer) ← 放回复用池 ⑥ del outputs ← Python对象可以被GC了 buffer准备好给下一次序列化使用八、一句话总结缓冲区: └── 连续内存块存放序列化后的字节数据 └── bytearray 在Python堆上管理 copyTrue: └── ZMQ复制一份数据到自己的内存 └── send()返回后buffer立即可用 └── 内存占用翻倍但简单安全 copyFalse (零拷贝): └── ZMQ直接引用Python的buffer内存 └── 不复制节省内存和时间 └── 必须等tracker.done才能复用buffer 缓冲区复用池: └── 避免频繁malloc/free └── 发送完成后buffer放回池子 └── 下次直接取出覆盖写入 └── 最多保留4个防止内存无限增长

相关新闻

DragonflyDB 与 Redis 核心对比

DragonflyDB 与 Redis 核心对比

1. 定位与起源:不同的时代,不同的哲学Redis​ 诞生于2009年,其设计深受早期单核CPU时代的影响。它的核心是单线程事件循环,这种设计带来了极致的简单性和惊人的低延迟,但也将性能束缚在单个CPU核心上。Redis的丰富数据…

2026/7/6 12:09:37 阅读更多 →
安全左移实践:需求阶段威胁建模

安全左移实践:需求阶段威胁建模

安全左移的范式革命在DevOps持续交付的浪潮中,"安全左移"从理念转化为刚需。需求阶段威胁建模作为安全左移的核心实践,使测试团队从传统漏洞检测者进阶为风险预防者。本文通过可落地的框架、工具链集成方案及测试团队转型路径,构建…

2026/5/17 8:48:16 阅读更多 →
TLS-S50~2000数显式弹簧拉压试验机

TLS-S50~2000数显式弹簧拉压试验机

TLS-S50~2000数显式弹簧拉压试验机一、弹簧拉压试验机特点及用途:TLS-S系列数显式弹簧拉压试验机依据JB/T7796-2017《弹簧拉压试验机技术条件》规定的设计。主机具有外形美观、操作方便、性能稳定可靠的特点。试验机采用集成电路、微机技术对位移、试验力…

2026/7/6 19:46:37 阅读更多 →

最新新闻

CCS Theia 安装多版本编译器.LTS

CCS Theia 安装多版本编译器.LTS

1.下载需要安装的编译器2.下载需要的版本:如:ti_cgt_c2000_22.6.3.LTS_windows-x64_installer.exe双击安装3.把需要安装的编译器安装到 CCS 的根目录,如:D:/ti如果有安装好的可以复制:把需要安装的编译器复制到 CCS 的…

2026/7/8 3:53:53 阅读更多 →
量化软件对比别只看排名:普通用户要拆三类工具

量化软件对比别只看排名:普通用户要拆三类工具

量化软件对比里,排名常常不如分类有用。普通用户先看能不能读懂规则、回测、提醒和风控;技术用户再看数据接口、策略函数和日志;接近实盘执行的人还要核对券商权限、委托和成交。牛股王股票这类面向普通投资者的量化辅助软件,适合…

2026/7/8 3:53:53 阅读更多 →
虚拟角色AI助手开发指南:从设定到工程化实现

虚拟角色AI助手开发指南:从设定到工程化实现

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 1. 先搞清楚这个标题到底在说什么 看到这个标题,很多人第一反应可能是“这是什么游戏角色还是AI助手?”其实这…

2026/7/8 3:53:53 阅读更多 →
B站视频转换终极指南:5分钟拯救你的珍贵缓存视频

B站视频转换终极指南:5分钟拯救你的珍贵缓存视频

B站视频转换终极指南:5分钟拯救你的珍贵缓存视频 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾有过这样的经历&#xff1f…

2026/7/8 3:51:52 阅读更多 →
计算机毕业设计之健康管理系统

计算机毕业设计之健康管理系统

随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&#xf…

2026/7/8 3:49:52 阅读更多 →
当 AI 生成的样式需要一间独立房间:Shadow DOM 与 CSS Modules 的样式隔离对决

当 AI 生成的样式需要一间独立房间:Shadow DOM 与 CSS Modules 的样式隔离对决

当 AI 生成的样式需要一间独立房间:Shadow DOM 与 CSS Modules 的样式隔离对决 一、深度引言与场景痛点 AI 生成的组件代码带着自己的样式行李——一段 CSS 声明打包在组件内部,颜色、间距、字号都是自给自足的。当这段代码进入设计系统仓库,…

2026/7/8 3:45:51 阅读更多 →

日新闻

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: …

2026/7/8 0:00:48 阅读更多 →
MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N1 到批处理的全路径 一、从"功能正确"到"性能可接受"——MyBatis 批量操作的三段式进化 MyBatis 在日常增删改查场景中几乎是无感的——实体映射直观、SQL 控制灵活。但当数据量从千级上升到十万级、百万级,许…

2026/7/8 0:00:48 阅读更多 →
工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

1. 工业负载控制方案概述在工业自动化、电机驱动和照明控制等高需求场景中,可靠地控制电感和电阻负载是核心挑战之一。TPD2015FN作为东芝的8通道高端智能功率开关IC,配合PIC18F45K22微控制器,能够构建一套稳定、高效的负载控制系统。这套组合…

2026/7/8 0:02:48 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/7 14:24:45 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/7 12:34:47 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/7 15:59:06 阅读更多 →

月新闻