新闻详情

新闻详情

首页 / 资讯中心 / 详情

SGLang Omni 场景下 Batch Size 调优:吞吐与延迟的博弈

发布时间:2026/10/1 12:29:19来源:尧图网络
SGLang Omni 场景下 Batch Size 调优:吞吐与延迟的博弈
上周我们组开了个评审会差点为一个小数点干起来——有人把在线推理服务的 Batch Size 从 8 改到 32压测数据里吞吐从 900 涨到 1500他看着曲线兴高采烈另一边的同学盯着 p99 从 400ms 涨到 2600ms脸色已经不太对。两边都觉得自己没错一个说“GPU 都满了凭什么不让我加”另一个说“用户都卡成幻灯片了你还谈吞吐”。这个争论本身不新鲜但如果把它放在 SGLang Omni 的场景里就变得特别有嚼头。先解释一下标题里的 Omni 指的不是机器人底盘上那种“全向轮”网上确实常有人把 SGLang 和“omni全向轮”凑在一起炒词其实在 LLM 语境下Omni 指的是能同时处理文本、图像、音频的多模态全向感知模型——比如 Qwen2.5-Omni、GPT-4o 这类。这类模型的输入 token 结构极其不均匀一张图可能就是上千个 token一句话可能只有几十个 token于是“Batch Size 调大一定划算”这个在纯文本时代还算靠谱的经验到了 Omni 场景里就经常翻车。这篇文章不打算给你一个“标准答案参数”因为本来就不存在。我想把那次争论从头到尾拆开Batch Size 在 SGLang Omni 里到底怎么影响性能、性能验证怎么做才算数、调度取舍背后的逻辑是什么。如果你也在维护 LLM/多模态推理服务或者正要给团队定性能验证规范这篇应该能帮你省掉不少吵架的时间。1. 这场争论的核心Batch Size 调大真能把吞吐翻倍吗1.1 争论双方的出发点都没错但观察口径不同先说“吞吐派”的逻辑。他们的思路来自训练时代GPU 是 SIMT 架构一次处理的数据越多计算单元越不容易空闲。Batch Size 从 8 涨到 32单个 step 里并行处理的请求多了GPU 利用率自然上去总吞吐往上走是符合直觉的。离线压测也支持这一点——固定 prompt 集、固定输出长度、没有并发波动纯批处理模式下吞吐确实接近线性增长。“延迟派”的理由也很硬。在线推理服务里用户请求是随机到达的Batch Size 调大意味着单次调度窗口里要等更多请求凑齐或者单轮 prefill/decode 的处理时间被拉长。最直接的后果是排队时间上升、尾部延迟恶化。对交互式应用来说p99 突破 2 秒基本等于产品不可用——哪怕吞吐翻倍用户也不会夸你快。两边吵不出结果的根本原因是各自拿的指标根本不是同一个量纲。吞吐派看的是“系统产出”延迟派看的是“单请求体验”这俩天然互相拉扯。更麻烦的是两边都没有把 Omni 模型的输入结构放进去考虑这才是争论一直悬而未决的深层原因。1.2 Omni 模型的变长输入为何让简单估算失效纯文本模型哪怕输入长度有波动分布也相对集中——你很难遇到一个请求是 30 个 token、另一个请求是 3000 个 token 的情况多数业务在同一个数量级内浮动。但 Omni 模型不一样一个不带图的文本请求可能只有 100 个 token一个带高清图的请求光图像部分就有 1500 token如果还有音频再叠几百个 token。于是同一个 batch 里各请求的 prefill 开销可能差出一两个数量级。这不只是“输入变长”这么简单。prefill 阶段是计算密集型的要对所有输入 token 做并行注意力计算显存占用和计算量随序列长度增长decode 阶段是访存密集型的要逐个 token 生成显存主要由 KV cache 撑住。当 batch 里既有几百 token 的短文本又有几千 token 的多模态请求时调度器会面临一个很尴尬的局面要么让长请求的 prefill 把算力吃光短请求在后面干等要么限制 batch 总量让短请求先走但 GPU 又喂不饱。SGLang 本身有应对手段比如分块 prefillchunked prefill和连续批处理continuous batching这些机制能让长请求的 prefill 被切碎插入 decode 间隙避免单个大请求堵住整条流水线。但机制归机制Batch Size 设置过大时任何调度算法都无法同时满足“高吞吐”和“低尾延迟”——这是一个物理约束不是调参能绕过去的。1.3 先算一笔粗糙的账看收益在哪个区间消失为了把问题说清楚我用一个简化模型估算一下。假设单请求平均输入 500 token、输出 200 tokenbatch 从 1 增加到 8 时GPU 计算单元逐渐被填满吞吐接近线性增长继续涨到 16、32如果请求长度分布均匀吞吐增长开始放缓——因为显存中 KV cache 的占用在加速调度器能同时塞进一个 decode 周期内的请求数反而受限当部分请求是 2000 token 的图像输入时batch 越大长请求等待短请求完成的情况越严重系统整体的有效吞吐甚至可能进入平台期。下面这个表是示意数据规律是真实环境中反复出现的但具体数值请以你自己的压测为准Batch 设置单请求平均时延p99 时延系统吞吐显存峰值观察现象小如 8450ms800ms900 tok/s35 GB延迟稳定GPU 利用率偏低中如 16600ms1200ms1400 tok/s50 GB吞吐明显上涨延迟可接受大如 321100ms2600ms1500 tok/s68 GB吞吐涨幅不到 10%尾延迟爆炸看到没有从 16 到 32吞吐只涨了 100 tok/s但 p99 翻了一倍多。这个“收益消失点”在纯文本模型里往往出现在更大的 batch 区间而在 Omni 场景下会提前很多。如果你只盯着吞吐曲线永远看不到第二张表——因为增长放缓的那一段恰恰是尾部延迟开始恶化的区间。2. 先看清 SGLang 调度器里 Batch Size 的真实身份2.1 你配的其实不是 Batch Size而是并发水位很多人第一次配 SGLang 时到处找batch_size参数结果发现根本没有这个字段。这其实是个信号SGLang 这类现代推理框架已经不做固定 batch 的训练式推理了而是用continuous batching——请求到达后进入队列调度器每个步进决定哪些请求进入执行池已经生成完的请求立刻让位给新请求整个 process 里并发数量是动态流动的。实际操作中你通过两个参数控制这个水位--max-running-requests最大同时在执行阶段的请求数和--max-num-batched-tokens单步最多处理的 token 数。前者控制并发请求数量后者控制单步计算量。也就是说“把 Batch Size 调大”这个动作在 SGLang 里对应的是放开这两个水位。但水位放开的代价不是立刻显现的——因为调度器不会把 32 个请求强行绑成一个 batch而是让它们自然流动于是你看到的效果往往是“平均 batch 上去了”而不是“batch 固定等于 32”。2.2 RadixAttention 与 KV Cache 复用显存账怎么改写SGLang 有个很核心的特性叫RadixAttention它会缓存请求前缀的 KV state并在新请求到来时复用。这个机制对多模态场景尤其重要因为 Omni 服务的系统提示词、固定指令、甚至某些业务里反复出现的图像 prompt 都是天然的可复用前缀。如果一批请求共享同一个长提示词RadixAttention 可以让这部分 KV cache 只需要计算一次后续请求直接命中缓存。这给 Batch Size 调优带来的影响是显存压力不会随并发数线性上涨。同样是 batch 32如果 30 个请求命中了同一个长前缀缓存实际新增的 KV cache 可能只相当于纯文本 batch 8 的量级反过来如果 32 个请求全是不同内容的图像输入前缀完全无法复用显存会瞬间崩掉。所以你看到有人把--max-running-requests调得很高还跑得好好的别急着抄——他的业务前缀复用率高你的可不一定。这部分也解释了为什么“看别人配置抄作业”在 SGLang 里特别不靠谱。你抄的是一组参数但背后是对方的业务输入结构、前缀复用率、GPU 富余程度任何一项不同结果就完全不一样。2.3 max_num_batched_tokens 与分块 prefill真正管住长尾的约束SGLang 里另一个容易忽略的参数是--max-num-batched-tokens。它限制的是单步调度最多容纳的 token 总数而不仅仅是请求条数。这个参数在 Omni 场景下比max-running-requests更关键——因为多模态请求的 token 差异太大10 个带图请求的 prefill 算力需求可能顶得上 100 个文本请求。当总 token 数超过预算时SGLang 会把 prefill 阶段的请求切成 chunk插入 decode 请求的空隙里执行避免一个超长 prefill 把所有 decode 请求堵死。这个机制叫分块 prefill。那么问题来了如果你把max-num-batched-tokens设得极大等于告诉调度器“你随便造”一旦几个大图请求同时进来prefill 就会长时间占住 GPUdecode 中的交互请求 p99 直接爆表设得太小又会让调度器频繁切块增加调度开销吞吐上不去。所以争论中的“Batch Size 调到 32 好不好”其实是个被问错的问题。真正要回答的是当前业务下并发水位和 token 预算分别应该放到多少才能让调度器在吞吐和延迟之间落在期望的位置上。参数不是拍脑袋定的是靠验证定的。3. 性能验证怎么做才能让争论有个结论3.1 先把验证口径统一再谈跑数据那次争论之后我意识到团队里没有一份“什么才算性能达标”的公共定义这才是吵架的本质。SGLang Omni 服务上线前至少要把这几个指标定下来首 token 时延TTFT从请求发出到收到第一个 token 的时间交互式场景最敏感单请求总时延完整生成完毕的时间离线任务更看重吞吐系统每秒产出的 token 数注意区分输入和输出显存峰值压测期间的显存上限决定你会不会 OOM前缀命中率RadixAttention 的缓存命中比例这个数字不记录吞吐数据根本无法跨场景对比分位数方面不要只看平均值。平均值是最容易被长尾污染的指标——一个 5 秒的超时请求就能把 20 个 300ms 请求的平均时延拉到 500ms 以上。我建议至少记录 p50、p95、p99 三档如果 SLA 有硬性要求还要记录超时比例比如“p99 必须低于 1200ms且超时率低于 0.1%”。3.2 负载生成器怎么造才不会自欺欺人验证性能最常犯的错是用一个固定 prompt 集、固定输出长度去压服务。那种测法只能验证“系统最理想状态下的吞吐上限”验证不了真实用户体验。真实业务里请求随机到达、输入长度不同、有的带图有的不带图、有的高优先级有的低优先级这些都要在压测里模拟出来。我给出一个最简负载脚本的思路用 Python 的asyncio实现泊松到达import asyncio import random import time import httpx URL http://localhost:30000/v1/chat/completions # 模拟多模态请求按业务比例混合文本和图文 def build_payload(): if random.random() 0.3: content [{type: text, text: 描述这张图片的内容}, {type: image_url, image_url: {url: https://example.com/sample.jpg}}] else: content 这是一个普通的文本请求用于模拟交互式对话。 return {model: omni-model, messages: [{role: user, content: content}], max_tokens: random.randint(64, 256)} async def send_one(client, sem, results): payload build_payload() start time.perf_counter() async with sem: try: async with client.stream(POST, URL, jsonpayload) as resp: first_token_time None async for chunk in resp.aiter_bytes(): if first_token_time is None and chunk: first_token_time time.perf_counter() - start total time.perf_counter() - start results.append({ttft: first_token_time, total: total}) except Exception as e: print(request failed:, e) async def main(duration300, qps5): sem asyncio.Semaphore(50) # 控制最大并发模拟 max-running-requests results [] async with httpx.AsyncClient(timeout120) as client: tasks [] start time.perf_counter() while time.perf_counter() - start duration: tasks.append(asyncio.create_task(send_one(client, sem, results))) await asyncio.sleep(random.expovariate(qps)) # 泊松到达 await asyncio.gather(*tasks) return results if __name__ __main__: data asyncio.run(main()) totals sorted(r[total] for r in data) ttfts sorted(r[ttft] for r in data) n len(totals) print(f请求数: {n}) print(fp50 total: {totals[n//2]*1000:.1f}ms, p99 total: {totals[int(n*0.99)-1]*1000:.1f}ms) print(fp50 ttft: {ttfts[n//2]*1000:.1f}ms, p99 ttft: {ttfts[int(n*0.99)-1]*1000:.1f}ms)这个脚本很粗糙但能解决一个关键问题让请求按随机时间间隔到达而不是一次性全部打进去。qps参数控制平均到达速率Semaphore模拟并发水位跑个 5 分钟就能看到排队效应。如果你有线上流量日志直接回放是最理想的方式——做不到的话用这个脚本也能凑合。负载模型定下来之后还要固定数据集的构成比例。比如 70% 纯文本、30% 带图图片分辨率要用业务里真实出现的档位不要全用同一张缩略图。图像 token 数差异很大你用 512x512 的图和用 1024x1024 的图测出来的吞吐可能差 3 倍。3.3 我在验证中踩过的坑每一条都是真金白银第一预热不足。SGLang 刚启动时 RadixAttention 缓存是空的前几十个请求全部是冷启动 prefill性能数据会明显偏低。正确做法是先跑 5-10 分钟预热流量让缓存热起来再开始采集数据。第二只看平均延迟不看分位数。有一轮压测我看平均时延 800ms觉得还行后来导数据发现 p99 到了 3.8 秒——平均数是把极少数超长请求和大量短请求揉在一起的结果。从那以后我只信分位数。第三显存只看 nvidia-smi 的当前值。这是个大坑。nvidia-smi 默认显示的是瞬时占用压测中的显存峰值可能出现在你眨眼的功夫。SGLang 启动日志里会打印max_gpu_memory之类的分配上限压测时要用nvidia-smi -l 1持续记录或者看服务的显存监控告警否则 OOM 总是毫无预兆。第四三轮取中位数不是取平均值。压测结果受系统噪音影响很大同一个配置跑三次取中位数比取平均值更能反映稳定水平。平均值会被某一次 GC 或者网络抖动拉偏而取中位数能有效屏蔽偶发因素。4. 调度取舍的本质不是“选个数字”而是“让谁先走”4.1 交互式场景延迟预算就是硬约束如果你的服务面向在线 Agent、聊天助手、实时翻译这类交互应用用户的耐心是以秒计的。这种场景下延迟预算是不可谈判的硬约束吞吐是次要优化目标。我的做法是把--max-running-requests控制在比较保守的范围比如按 CPU 核心数和显存推算出一个不会导致排队雪崩的并发上限然后用压测调低直到 p99 落在 SLA 以内。交互场景还需要考虑优先级。SGLang 支持请求排队但默认策略对长短请求相对公平——这在交互场景里不够。比如一个用户正在等首 token另一个离线任务在后台批量生成摘要你显然希望前者插队。实际配置里你可以通过入口网关给在线请求打上高优先级标签或者干脆把在线和离线拆成两个服务实例让调度器互不干扰。4.2 离线批处理场景把吞吐榨干但要防 OOM离线评测、批量数据生成、知识库向量化这类场景用户不关心单请求时延只关心单位时间能处理多少请求。这时可以把并发水位和 token 预算全部拉高让调度器满载运行。但即使在这里也不是无限调大最好——因为显存是硬边界KV cache 占满之后会触发 OOM整个批量任务都会断掉。我常用的策略是先把显存上限设到可用显存的 85%-90%跑一轮大并发压测观察显存峰值是否稳定如果稳定再逐步加并发直到吞吐增长趋缓或显存逼近边界为止。这个“逐步逼近”的过程很笨但很可靠。另外离线任务最好加断点续传或任务队列万一中途 OOM能恢复而不是从头再来。4.3 混合负载怎么办先想清楚能不能共用很多团队只有一台 GPU 服务器白天在线交互晚上离线批量然后试图用一组参数兼顾两种负载。我的经验是如果在线流量是主营业务别贪这点吞吐直接按在线 SLA 配参数离线任务排队就排队。因为离线任务可以晚几个小时跑完在线用户等不了。如果你确实想榨干硬件可以用两个推理实例或多个命名空间隔离负载——在线实例保守配置保延迟离线实例激进配置榨吞吐。一台机器上同时跑两个实例需要预留隔离的显存配置上多费些心但比挤在一个实例里互相拖累要省心得多。如果连两个实例都开不了那就只能靠入口限流和优先级排队了这属于“用软件调度弥补硬件不足”效果有限但总比裸奔强。4.4 一张决策表把取舍逻辑固定下来围绕“让谁先走”这个核心问题我在团队里沉淀了一张决策表每次新项目上线直接套用业务场景硬约束优先指标推荐动作主要风险在线交互 Agentp99 1.5s首 token 时延、p99保守并发水位开优先级吞吐偏低GPU 利用率不足离线批量生成无延迟要求吞吐、显存峰值高并发水位逐步逼近显存边界OOM 导致任务中断混合负载在线 SLA在线 p99 优先拆双实例或按时间段切换配置切换期间配置参数错误高前缀复用业务显存优先前缀命中率、吞吐大胆并发依赖 RadixAttention缓存命中率变化导致显存突变这张表不解决具体参数但每次争论出现时先往表里套一下——业务属于哪一行、硬约束是什么吵架的方向就清晰多了。技术方案从来不是非黑即白关键是先找准约束条件。5. 复盘一次完整验证从争议到拍板的全过程5.1 实验环境与压测设计那次争论最终变成了“跑数据谁也别嘴上说了算”。我们用的环境是一张 80GB 显存的 GPU模型是多模态 Omni 类 7B 模型SGLang 启动命令大致长这样python -m sglang.launch_server \ --model-path your-omni-model \ --port 30000 \ --max-running-requests 64 \ --max-num-batched-tokens 8192 \ --mem-fraction-static 0.85 \ --enable-mixed-chunk \ --trust-remote-code注意--max-running-requests和--max-num-batched-tokens这两个值我一开始也是凭经验拍的后续压测过程中不断调整。负载脚本就是上面给的泊松到达版本混合了 70% 文本和 30% 图片请求qps 按线上峰值的 0.8 倍设置预热 10 分钟正式压测 20 分钟每组配置跑三轮取中位数。5.2 三组配置的实测结果对比我们对比了三组配置保守并发 16、token 预算 4096、默认并发 32、token 预算 8192、激进并发 64、token 预算 16384。数据经过脱敏处理但规律是真实完整的配置p50 总时延p99 总时延吞吐显存峰值前缀命中率保守620ms1100ms780 tok/s42 GB61%默认780ms1500ms1240 tok/s55 GB59%激进1900ms5300ms1420 tok/s74 GB57%看到激进组的 p99 直接到了 5.3 秒那一刻延迟派的同事赢得了辩论。但真正有价值的信息是从保守到默认吞吐涨了 460 tok/sp99 只涨了 400ms这笔交易非常划算从默认到激进吞吐只涨 180 tok/sp99 却翻了 3.5 倍这笔交易血亏。收益拐点就在默认配置附近——不是巧合是因为 token 预算 8192 恰好匹配我们的平均请求长度和图片占比。显存数据也很有意思。激进配置离 80GB 上限只差 6GB一旦有比测试集更长的图片请求进来OOM 几乎是必然事件。前缀命中率三组都比较稳定57%-61%说明这个业务的缓存复用不算高显存压力主要是请求本身的多样性撑起来的。5.3 最终怎么拍板在线和离线分开玩数据出来之后结论就没什么好争的了。在线交互服务采用默认配置附近的值实际线上调成并发 24、token 预算 8192给 p99 留了约 30% 的安全余量防止流量波动时触发超时。离线批量任务通过另一个端口开了第二个实例用激进配置跑吞吐接近 1400 tok/s偶尔延迟高到离谱也没人在意——它本来就不面向用户。另外我们把那套压测脚本、数据集、三轮取中位数的规范固化成了团队内部的 benchmark 工具包。后面任何模型升级、SGLang 版本变更、显存卡更换都先跑一遍标准压测把 p50、p99、吞吐、显存峰值、前缀命中率五组数贴在项目文档里再讨论要不要改参数。那次争论给我最大的收获不是什么高深的优化技巧而是一个朴素的道理性能问题别用嗓门解决用可复现的压测口径解决。在会议室里争一千句“我觉得”不如一张按统一方法跑出来的 p99 曲线。最后分享一个我后来一直保留的小习惯每次调完调度参数除了看吞吐和延迟一定顺手记一下 RadixAttention 的前缀命中率和压测数据集的平均输入长度。这两个数字不会直接告诉你“该调什么”但当前缀命中率突然掉了五个点或者平均输入长度涨了 30%你就能提前意识到调度压力要来了而不是等 p99 爆掉之后才回头找原因。做 SGLang Omni 服务调优大部分时间不是在调参而是在读懂系统在你业务负载下的脾气。摸清了脾气参数自己会说话。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Win11修改用户名与用户文件夹全攻略:注册表路径替换教程,避免系统崩溃 2026/10/1 19:09:21

Win11修改用户名与用户文件夹全攻略:注册表路径替换教程,避免系统崩溃

很多朋友拿到Win11新电脑,或者用了很久之后,总觉得系统里的用户名看着别扭。这个用户名指的是登录时显示的名字,也是C盘用户文件夹的名字(C:\Users\用户名)。想改掉它,看着是小事,动手才发现坑不…

阅读更多 →
AI-Native落地关键:企业知识库架构设计与RAG检索增强实战解析 2026/10/1 19:09:15

AI-Native落地关键:企业知识库架构设计与RAG检索增强实战解析

我们团队在推进 AI-Native 项目落地时,撞上的第一堵墙不是模型能力,而是知识。明明接入了市面上最强的大语言模型,业务侧一提问,回答要么是含糊其辞的“正确的废话”,要么是自信满满地编造一个不存在的产品参数。后来我…

阅读更多 →
COZE低代码AI平台:5大核心能力+2类交付物实战解析 2026/10/1 19:09:15

COZE低代码AI平台:5大核心能力+2类交付物实战解析

1. 项目概述:为什么“5.2平台一:COZE”突然成为高频搜索词? 最近两周,我在三个不同行业的客户群里都看到同一个词被反复提起——“5.2平台一:COZE”。不是“coze怎么用”,也不是“coze和dify哪个强”&#…

阅读更多 →
AI日报从0到1:信息源筛选、选题取舍与内容框架设计方法论 2026/10/1 19:09:15

AI日报从0到1:信息源筛选、选题取舍与内容框架设计方法论

1. 一份 AI 日报的定位与内容框架设计做 AI 日报这件事,我从 2024 年断断续续做到现在,中间停更过两次,也换过三套模板。2026 年 9 月 25 日这一期,是我目前跑得最顺的一版结构,所以拿它当样本,把整套方法论…

阅读更多 →
基于深度学习的区域电力负荷预测:LSTM时序建模与工程实践指南 2026/10/1 19:09:15

基于深度学习的区域电力负荷预测:LSTM时序建模与工程实践指南

简介:这是一份基于深度学习实现区域电力负荷预测的完整项目工程,面向深度学习课程设计、期末大作业及电力数据预测入门实践者。项目已获导师指导并取得97分高分,代码组织清晰,涵盖了数据预处理、模型构建、训练评估与可视化等关键…

阅读更多 →
Docker部署MariaDB生产实践:从容器化到数据持久化与高可用 2026/10/1 19:09:02

Docker部署MariaDB生产实践:从容器化到数据持久化与高可用

这几年数据库容器化已经不是什么新鲜话题了,但真正敢把生产环境的 MariaDB 跑在容器里的人,仍然比想象中少。原因倒也不难理解:数据库是有状态服务,跟无状态的 Nginx、Redis 不一样,数据丢了就是事故。我在实际项目中用…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉