大模型推理的批处理优化——Continuous Batching 与 Dynamic Batching 对比
大模型推理的批处理优化——Continuous Batching 与 Dynamic Batching 对比一、批处理的本质问题大语言模型LLM的推理性能瓶颈主要集中在 GPU 计算资源的利用效率上。与传统的深度学习模型不同LLM 推理是自回归的——每个 token 的生成都依赖于前面所有 token。这种串行特性使得单个请求的 GPU 利用率极低通常在 30%-50%。批处理的核心思想是将多个推理请求合并执行提高 GPU 的计算密度。但在自回归解码场景下不同请求的长度差异、到达时间差异和生成速度差异使得传统的静态批处理存在严重浪费。本文将深入对比 Continuous Batching 和 Dynamic Batching 两种主流方案。二、传统 Batching 的问题gantt title 传统静态批处理的 GPU 利用率问题 dateFormat X axisFormat %s section 请求A短 Prefill :a1, 0, 2 Decode :a2, 2, 4 Idle :a3, 4, 10 section 请求B中等 Prefill :b1, 0, 3 Decode :b2, 3, 8 Idle :b3, 8, 10 section 请求C长 Prefill :c1, 0, 2 Decode :c2, 2, 10传统批处理需要等所有请求都完成后才能释放资源。短请求完成后GPU 计算单元处于空闲状态等待长请求完成——这就是木桶效应。三、Continuous Batching 的原理Continuous Batching连续批处理由 vLLM 首创核心创新在于在每个 decode 步骤之后重建 batch。当一个请求完成生成时立即将其从 batch 中移出当有新的请求到达时可以动态加入正在执行的 batch。sequenceDiagram participant B as Batch Scheduler participant G as GPU(Kernel) Note over B,G: 初始状态3个请求在运行 B-G: Step 1: [Req1, Req2, Req3] Note over G: 并行解码一个token Note over Req1: Req1 生成完成EOS B-G: Step 2: [Req2, Req3, Req4] ✨ Note over B: Req1移出Req4动态加入 Note over Req2: Req2 生成完成 B-G: Step 3: [Req3, Req4, Req5] ✨ Note over B: Req2移出Req5动态加入Continuous Batching 的核心数据结构——Block-wise KV Cache 管理器/** * 分块 KV Cache 管理器——Continuous Batching 的核心数据结构 * * 每个 Block 存储固定数量 token 的 KV Cache * 通过跳表实现 O(log N) 的查找和 O(1) 的释放 */ public class PagedKVCacheManager { /** 每个 Block 可存储的 token 数量 */ private static final int TOKENS_PER_BLOCK 16; /** Block 大小 TOKENS_PER_BLOCK * num_layers * num_heads * head_dim * dtype_size */ private static final int BLOCK_SIZE_BYTES 16 * 32 * 32 * 128 * 2; /** GPU 显存中可用的 Block 总数 */ private final int totalBlocks; /** 空闲 Block 列表 */ private final ConcurrentLinkedQueueInteger freeBlocks; /** 每个请求的 Block 分配表requestId - ListBlockId */ private final MapString, ListInteger requestBlockTable; /** 已分配的 Block 引用计数 */ private final int[] blockRefCount; public PagedKVCacheManager(int totalBlocks) { this.totalBlocks totalBlocks; this.freeBlocks new ConcurrentLinkedQueue(); this.requestBlockTable new ConcurrentHashMap(); this.blockRefCount new int[totalBlocks]; // 初始化空闲块 for (int i 0; i totalBlocks; i) { freeBlocks.offer(i); blockRefCount[i] 0; } } /** * 为一个请求分配新的 KV Cache Block * * param requestId 请求标识 * return 分配的 Block ID无可用 Block 时返回 -1 */ public synchronized int allocateBlock(String requestId) { Integer blockId freeBlocks.poll(); if (blockId null) { log.warn(无可用 KV Cache Block请求 {} 需要等待或触发抢占, requestId); return -1; } // 记录分配 requestBlockTable .computeIfAbsent(requestId, k - new ArrayList()) .add(blockId); blockRefCount[blockId]; // 将新 Block 链接到上一个 Block ListInteger blocks requestBlockTable.get(requestId); if (blocks.size() 2) { int prevBlockId blocks.get(blocks.size() - 2); // 通过物理块链接指针建立逻辑顺序 log.debug(Block {} - Block {} (请求{}), prevBlockId, blockId, requestId); } return blockId; } /** * 释放一个请求的所有 KV Cache Block * 使用 Copy-on-Write 保证并发安全 */ public synchronized void freeRequest(String requestId) { ListInteger blocks requestBlockTable.remove(requestId); if (blocks null) { return; } for (int blockId : blocks) { int refCount --blockRefCount[blockId]; if (refCount 0) { freeBlocks.offer(blockId); } } log.debug(释放请求 {} 的 {} 个 KV Cache Block, requestId, blocks.size()); } }四、Dynamic Batching 的原理Dynamic Batching动态批处理是另一种优化思路。它不会在每个 decode 步骤后重建 batch而是通过请求长度对齐和分组调度来减少浪费/** * 动态批处理调度器——按请求长度分组执行 */ public class DynamicBatchingScheduler { /** 最大批处理大小 */ private static final int MAX_BATCH_SIZE 32; /** Prefill 阶段的分组长度窗口 */ private static final int LENGTH_ALIGNMENT_WINDOW 128; private final BlockingQueueInferenceRequest pendingQueue; private final ExecutorService gpuExecutor; /** * 按长度对请求分组每组内部长度接近以减少 padding 浪费 */ public void scheduleBatch() { while (!pendingQueue.isEmpty()) { // 收集待处理请求 ListInferenceRequest pending new ArrayList(); pendingQueue.drainTo(pending, MAX_BATCH_SIZE * 2); if (pending.isEmpty()) { break; } // 按输入长度排序 pending.sort(Comparator.comparingInt(InferenceRequest::getInputLength)); // 分组长度差距在 WINDOW 范围内的请求放入同一批次 ListBatchGroup groups new ArrayList(); BatchGroup currentGroup new BatchGroup(); currentGroup.add(pending.get(0)); for (int i 1; i pending.size(); i) { InferenceRequest req pending.get(i); int groupMinLength currentGroup.getMinLength(); if (Math.abs(req.getInputLength() - groupMinLength) LENGTH_ALIGNMENT_WINDOW currentGroup.size() MAX_BATCH_SIZE) { currentGroup.add(req); } else { groups.add(currentGroup); currentGroup new BatchGroup(); currentGroup.add(req); } } groups.add(currentGroup); // 提交每组到 GPU 执行 for (BatchGroup group : groups) { gpuExecutor.submit(() - executeBatch(group)); } } } /** * 执行一个批次的推理 */ private void executeBatch(BatchGroup group) { int maxLength group.getMaxLength(); // 对每个请求进行 padding 到组内最大长度 for (InferenceRequest request : group.getRequests()) { int padLength maxLength - request.getInputLength(); if (padLength 0) { request.padInput(padLength); } } // 执行 batched 推理 try { gpuRuntime.inference(group.getRequests()); log.info(Dynamic Batch 完成批次大小{}, 最大长度{}, padding浪费{}%, group.size(), maxLength, group.getPaddingWastePercentage()); } catch (Exception e) { log.error(Batch 推理执行异常, e); } } }五、方案对比维度Continuous BatchingDynamic Batching核心理念每个 decode 步重建 batch按长度分组组内对齐GPU 利用率极高接近 100%中等60%-80%实现复杂度高——需要 PagedAttention 和精细的内存管理中等——可在现有框架上改造内存效率Block 级分配零碎片连续内存存在 padding 浪费首 Token 延迟更优——新请求可随时加入需要等待当前批次完成代表实现vLLM、TensorRT-LLM、SGLangHuggingFace TGI早期版本、DeepSpeed适用场景高并发 API 服务离线批处理、评估、微调六、选型建议在线推理服务优先选择 Continuous Batching。如果使用 vLLM 或 TensorRT-LLM 部署它已经是默认行为。离线批处理任务Dynamic Batching 更简单可靠对于评估、数据标注等场景基本够用。混合场景部分框架如 SGLang结合了两者使用 RadixAttention 共享公共前缀的 KV Cache在 System Prompt 固定的场景下效果显著。生产环境的实际性能对比我们在同一套 A100-80G 硬件上对两种方案进行了基准测试测试条件为 100 并发请求、输入长度 500-2000 Token、输出目标 200 Token。Continuous BatchingvLLM v0.5.4的吞吐量为 2340 Token/s首 Token 延迟 P50/P99 分别为 180ms/520msDynamic Batching 的吞吐量为 1560 Token/s首 Token 延迟 P50/P99 分别为 340ms/1200ms。在并发度提升到 500 时Continuous Batching 的吞吐量线性增长到 11000 Token/s而 Dynamic Batching 仅增长到 4200 Token/s 就遇到了瓶颈——瓶颈不在 GPU 计算而在于请求等待进入批次的排队延迟。在内存效率方面Continuous Batching 的 PagedAttention 机制使 KV Cache 利用率达到 96.4%Block 内部浪费约 3.6%而 Dynamic Batching 受限于连续内存分配padding 导致的浪费高达 28%。以 80GB 显存的 A100 为例这意味着 Continuous Batching 可以同时服务约 2.3 倍的并发请求数。但 Continuous Batching 的代价也在工程复杂度上——需要实现精细的 GPU 显存管理包括 Defragmentation 和 Block 回收、调度器需要处理 Prefill 和 Decode 的优先级互斥长 Prefill 可能阻塞短 Decode以及 KV Cache Block 的跨请求共享机制。对于自建推理服务的团队如果维护资源有限Dynamic Batching 的运维成本更低但需要接受约 1.5-2 倍的成本效率差距。七、批处理优化的工程边界7.1 Continuous Batching 的适用边界虽然 Continuous Batching 能显著提升 GPU 利用率但并非所有场景都适合使用。在我们的实践中发现以下场景中 Continuous Batching 反而会降低性能单请求延迟敏感场景如实时对话系统用户期望首 Token 延迟TTFT500ms。Continuous Batching 为了最大化吞吐量会尽可能将多个请求合并执行这会导致单个请求的 TTFT 增加。解决方案是使用混合策略当队列深度 4 时禁用连续批处理直接单请求执行显存受限环境Continuous Batching 需要维护 Paged KV Cache 管理器这本身会消耗一定的显存约 5-10%。在显存紧张的环境如单卡部署大模型这可能成为瓶颈请求长度极度不均如果同时有 10 个 token 的短请求和 10000 个 token 的长请求Continuous Batching 会导致短请求等待长请求反而降低整体效率。解决方案是实现请求长度感知的调度策略将相似长度的请求分组处理。7.2 批处理大小与延迟的权衡批处理大小Batch Size的选择需要在吞吐量和延迟之间找到平衡。我们的测试数据显示基于 Llama 3 8B 模型A100 GPUBatch Size吞吐量 (tokens/s)TTFT (ms)每 token 延迟 (ms)GPU 利用率1520851835%832001202572%3278003804289%6492006505893%128980012008595%数据显示Batch Size 从 1 增加到 32 时吞吐量提升约 15 倍但 TTFT 只增加约 4.5 倍。超过 32 后吞吐量增加趋缓但延迟急剧上升。对于在线服务建议将最大 Batch Size 设置为 16-32并通过 A/B 测试找到延迟和吞吐量的平衡点。对于离线批处理任务如数据标注可以使用更大的 Batch Size64-128来最大化吞吐量。7.3 KV Cache 内存管理的工程实践Continuous Batching 的核心数据结构是 Paged KV Cache其内存管理直接影响系统稳定性和性能。我们的工程实践经验Block 大小选择需要根据模型特性和请求长度分布调整。对于长上下文模型如 32K tokens建议使用更大的 Block 大小如 32 或 64以减少 Block 切换开销。我们的测试显示Block 大小从 16 增加到 64吞吐量提升约 8%但显存碎片化风险也增加显存预留策略不要将 100% 的 GPU 显存都分配给 KV Cache。建议预留 10-15% 的显存作为缓冲区用于处理请求突增或长请求。在我们的实践中预留显存能将 OOMOut of Memory发生率从 5% 降低到 0.1%Block 回收优化当请求完成或中断时需要立即释放其占用的 Block。但如果释放不及时会导致显存碎片。我们实现了一个后台清理线程每 5 秒扫描一次 Block 引用计数回收不再使用的 Block。这个线程的CPU开销约为 2-3%但能将显存利用率稳定在 85-90%。八、总结Continuous Batching 代表了 LLM 推理优化的前沿方向其 Block 级 KV Cache 管理理念值得深入理解。在选择方案时需要根据在线/离线、并发量、延迟要求等维度综合评估。

相关新闻

分布式系统的一致性协议——从 Paxos 到 Raft 的工程化理解

分布式系统的一致性协议——从 Paxos 到 Raft 的工程化理解

分布式系统的一致性协议——从 Paxos 到 Raft 的工程化理解 一、为什么需要一致性协议 在分布式系统中,节点故障、网络分区、消息延迟是常态而非异常。当一个值需要在多个节点间达成一致时(例如选主、分布式锁、配置同步),就必须借…

2026/7/18 23:59:04阅读更多 →
SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径

SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径

摘要随着生成式人工智能技术的普及,用户获取信息的方式正从传统关键词搜索向AI对话式问答转变。据行业研究机构Gartner预测,到2026年,传统搜索引擎的流量份额可能下降超过25%,而AI驱动的搜索与推荐将成为B2B决策的重要入口。对于S…

2026/7/18 23:59:04阅读更多 →
statCard 构建器:统一数据统计卡片样式

statCard 构建器:统一数据统计卡片样式

前言 在“海风日记“的回顾页中,statCard 构建器封装了统计卡片的样式,用于展示日记篇数、连续天数和总字数等数据。 本文将从源码出发,深入讲解 statCard 构建器的设计和复用。 一、statCard 构建器 1.1 源码 Builder statCard(value: s…

2026/7/18 23:59:04阅读更多 →
三步彻底解决机械键盘连击问题:KeyboardChatterBlocker 实用指南

三步彻底解决机械键盘连击问题:KeyboardChatterBlocker 实用指南

三步彻底解决机械键盘连击问题:KeyboardChatterBlocker 实用指南 【免费下载链接】KeyboardChatterBlocker A handy quick tool for blocking mechanical keyboard chatter. 项目地址: https://gitcode.com/gh_mirrors/ke/KeyboardChatterBlocker 你是否在打…

2026/7/19 2:05:23阅读更多 →
Visual Studio C++动态库创建与使用全攻略:从原理到实战避坑

Visual Studio C++动态库创建与使用全攻略:从原理到实战避坑

1. 项目概述:为什么C开发者必须掌握动态库在Windows平台上用Visual Studio(VS)捣鼓C项目,无论是做桌面应用、游戏引擎还是工业软件,动态链接库(Dynamic Link Library, 也就是我们常说的.dll文件…

2026/7/19 2:05:23阅读更多 →
MSMQ同步机制设计与企业级应用实践

MSMQ同步机制设计与企业级应用实践

1. MSMQ同步机制基础解析MSMQ(Microsoft Message Queuing)作为Windows平台的核心消息队列服务,在企业级应用中扮演着异步通信管道的角色。但实际业务中经常遇到需要同步操作的特殊场景——比如金融交易处理需要即时反馈结果,或者库…

2026/7/19 2:05:23阅读更多 →
为什么现代开发者需要图形化SSH客户端来提升远程服务器管理效率?

为什么现代开发者需要图形化SSH客户端来提升远程服务器管理效率?

为什么现代开发者需要图形化SSH客户端来提升远程服务器管理效率? 【免费下载链接】nxshell An easy to use new terminal. 项目地址: https://gitcode.com/gh_mirrors/nx/nxshell 在远程服务器管理和运维工作中,SSH客户端是开发者和系统管理员最…

2026/7/19 2:05:23阅读更多 →
NG28相信品牌的力量

NG28相信品牌的力量

NG南宫大舞台相信品牌力量:https://wx.dsgj13.com/app/register.php?site_id2173&topId3715594该工具由有用科技开发,有用科技是超过10年技术沉淀的国产科技公司,秉持“做有用的软件”的品牌理念,已合作的知名品牌包括联想、…

2026/7/19 2:05:23阅读更多 →
C#与C++混合编程实战:WebSocket高效数据传输

C#与C++混合编程实战:WebSocket高效数据传输

1. 项目背景与核心需求在工业控制、游戏开发和金融交易等实时性要求高的场景中,C#与C的混合编程方案越来越常见。最近我在一个智能制造项目中遇到了这样的需求:需要通过WebSocket实时传输设备状态数据,但核心算法是用C编写的非托管DLL。经过多…

2026/7/19 2:03:23阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/18 22:49:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/18 14:49:24阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/18 18:49:35阅读更多 →