新闻详情

新闻详情

首页 / 资讯中心 / 详情

SGLang Omni 性能调优:Batch Size 调度与验证实战

发布时间:2026/9/30 9:48:53来源:尧图网络
SGLang Omni 性能调优:Batch Size 调度与验证实战
做推理服务的人都知道Batch Size 这四个字一旦摆上桌面基本就是一场小型辩论赛的开局。上个月我们在复盘一次线上性能波动时因为“Batch Size 到底该调大还是调小”争了将近两个小时一边说“批次越大吞吐越高不调大就是浪费 GPU”另一边拿着监控截图反驳“批次一大首 token 延迟直接起飞SLA 全被击穿”。到最后谁也没说服谁但这场争论倒是逼我们把 SGLang Omni 的调度机制、性能验证方法翻了个底朝天。这篇文章就是把那场争论背后我梳理出来的完整思路写下来希望能给正在折腾大模型推理服务、尤其是用 SGLang Omni 做多模态服务的同学一点参考。先交代背景。我们这边的服务基于 SGLang Omni同时承接文本和图文混合请求线上有比较严格的首 token 时间限制也会跑一些长文档理解任务。SGLang 这框架我实际用了大半年最大的感受是它把“调度”和“批处理”做得很深很多表面上的 Batch Size 问题本质上是调度策略、缓存策略和显存水位这几件事在互相拉扯。所以这篇不准备只讲“设置 batch size 的 number 多少合适”而是从性能验证和调度取舍两个角度把 SGLang Omni 在真实负载下怎么表现、怎么测、怎么调尽量一次性说透。1. 一次 Batch Size 争论背后的真实矛盾1.1 “大不了调大”和“调大必死”两个流派那天争论的起因很普通。线上服务偶发超时某位后端同事看了眼监控发现 GPU 利用率只有 60% 左右立刻得出结论“Batch Size 开小了调度器太保守应该把平时最大 batch 调大让每次迭代多塞几条请求进去。”这话在纯推理吞吐测试里没毛病因为 batch 越大矩阵乘法的计算密度越高单位 token 的算力成本越低吞吐量曲线前期基本线性上涨。但另一位负责接入层的同事直接否了“你看看 p99 首 token latency已经快 2 秒了再调大 batch首 token 还得更慢用户早就走了。”他们俩其实都没说错只是看的指标不一样。前者看的是 E2E 吞吐和 GPU 利用率后者看的是交互式请求的感知延迟。在大模型推理里这两个目标天然存在张力想提高吞吐就要把更多序列塞进同一个 decode 迭代里跑但这会让每一条请求都得排队等前一轮 token 生成完才能拿到新 token首 token 自然就被拖慢了。SGLang 这种框架虽然用了 continuous batching能细粒度地在迭代中间插入新请求但总体的矛盾并没有消失只是被调度器变得“可控”了。1.2 为什么单看指标会吵起来再往深一层说这场争论暴露的是“性能验证维度不统一”的问题。吞吐派拿的曲线是requests per second和GPU utilization延迟派拿的曲线是TTFT和inter-token latency两边拿的是同一批压测数据但因为没有把“负载模型”交代清楚结论完全对不上。比如你用固定并发数去压测请求全都是短查询平均 50 个 token 就结束了那么调大 Batch Size 带来的收益非常明显。可一旦混入长文档问答一条请求要跑几千 tokendecode 阶段的序列长度很快飙升KV cache 占用和单步计算量同时变大再保持大的 batch就会出现明显的尾部延迟。所以只要把“请求长度分布”和“时间窗口内并发模式”这两件事说清楚争论立刻会从“调大还是调小”变成“在什么负载模型下调大能收益多少”。1.3 SGLang 里 Batch Size 到底指什么这里要特别说明一下SGLang Omni 里你通常不会直接看到一个叫batch_size的硬配置。它的调度核心是Scheduler它维护了一个running batch里面是当前正在参与 decode 的请求以及waiting queue里面是排队中的请求。每个 iteration 结束前调度器会根据显存余量、前缀缓存命中情况、max running requests 这些约束决定要不要从 waiting queue 里再捞几条请求进 running batch。所以我们在 SGLang 里所说的“调 Batch Size”实际动的是这么几个参数--max-running-requests限制同时 in-flight 的请求数量--max-total-tokens限制所有 running batch 的总 token 数还有后端 kernel 层面类似--cuda-graph-max-batch-size这种决定 CUDA graph 捕获的最大 batch 形状。这几层都影响实际单步 batch 多大但最直接、最好理解的是max running requests和max total tokens这两个水位。2. SGLang Omni 的性能验证正确打开方式2.1 别只测平均延迟四个核心指标那场争论之后我们重新整理了一套 SGLang Omni 的验收指标不搞“一个平均值走天下”而是按四个维度拆开看TTFTTime To First Token用户发出请求到收到第一个生成 token 的耗时Inter-Token Latency或TPOT生成阶段相邻 token 的时间间隔E2E Latency整条请求从发起 到结束的耗时Throughput单位时间完成的请求数或生成的 token 数。这四个维度单独看都有盲区但只要组合起来就能精准定位是调度排队的问题还是 decode 计算的问题。举一个我们实际踩过的例子有次压测显示平均 TTFT 只有 180ms看起来挺好但 p99 TTFT 直接飙到 4.6 秒。查完 pending queue 发现某些长 prompt 的多模态请求在 prefill 阶段算得太久把后面所有请求都顶住了。如果只看平均延迟你根本看不到那部分用户的真实体感有多差。所以我现在所有性能验证报告都强制要求列 p50 / p95 / p99并且按“短文本”“长文本”“图文混合”三个场景分开统计。2.2 压测场景设计与工具选择压测工具我们试过不少像wrk、hey、locust都有各自的脾气但大模型服务跟普通 Web 服务最大的区别是不是每发一个请求就结束了它会持续占用连接直到生成完所有 token。所以压测必须要能模拟“并发 长连接 流式输出”这三种特性工具至少要支持流式读取否则压出来的结果误差很大。我们最后用的是自己写的一个 Python 压测脚本基于httpx的 stream 接口控制并发数之后每个 worker 循环发请求累积完一轮 500 个请求就统计一轮指标。代码结构很简单核心部分大概是这样的import asyncio import httpx import time import statistics async def one_request(client, pid): start time.perf_counter() first_token None tokens 0 async with client.stream(POST, URL, jsonbuild_payload(pid)) as resp: async for chunk in resp.aiter_bytes(): if first_token is None: first_token time.perf_counter() - start tokens chunk.count(b ) return { pid: pid, ttft: first_token, e2e: time.perf_counter() - start, tokens: tokens, } async def main(): results [] async with httpx.AsyncClient(timeouthttpx.Timeout(None)) as client: tasks [one_request(client, i) for i in range(CONCURRENCY * ROUNDS)] for task in asyncio.as_completed(tasks): results.append(await task) # 这里再按短文本/长文本/图文混合分组统计 p50/p95/p99要注意给压测脚本的请求 payload 做随机化。我见过不少同学压测时反复用同一段 prompt结果吃掉的全是 RadixAttention 前缀缓存红利测出来吞吐高得离谱上线后真实流量一来立刻打回原形。正确的做法是准备至少 30 组不同的 prompt 模板配合不同的图片尺寸和 token 数量轮流注入负载才能反映真实的 prefill 成本。2.3 聊 SGLang Omni 多模态场景下要额外看什么SGLang Omni 这套本身是要支持视觉、音频等多模态输入的推理框架和纯文本的 LLM 推理相比性能验证又多出两个变量。第一个是视觉编码器这部分的耗时通常来说是前置的一个图片进来先做 vision encoding再拼进文本序列里做 prefill。第二个是图像带来的长 token 序列一张高分辨率图片动辄几百甚至上千个视觉 token这会让 prefill 阶段的计算量暴涨直接影响后面排队请求的 TTFT。所以多模态压测必须单独记录image encode time和first token after image这部分耗时。比如我们有项业务用户上传图片加文字混合提问单请求的总 input token 平均到了 1500其中图片 token 占了一半以上。SGLang 本身对视觉 token 的拼接和缓存有自己的处理机制但该慢还是会慢最有效的优化其实是把多模态请求和纯文本请求的队列分开防止图片 prefill 把文本请求全部堵死。这一点在线上跑混合负载时尤其重要。2.4 可复现性能验证的三条纪律第一每次压测前记录基线包括模型权重格式、SGLang 版本、CUDA graph 开关、数据并行数、显存总量把这些全写进测试报告否则两周后另一台机器复测数据对不上你根本不知道变化来自代码还是环境。第二先做单请求隔离测试再做并发压测。单请求测试的意义是拿到“该模型能达到的最优延迟”这块可以验证框架本身有没有引入额外开销并发压测才是看调度器的真实水平。我在线上环境跑 SGLang 经常发现单请求延迟还不错但并发拉到 40 之后 TTFT 陡增这种情况多半是请求排队和 prefill 抢占导致的。第三每改一个参数都要单独验证。Batch Size 改了就只动 Batch Size调度策略改了就不要同时改缓存参数。能用一个变量解释问题就不要同时引入两个变量这是性能验证里最基本的科学方法但也是团队里最容易违反的一条。3. 调度取舍为什么“调大 Batch”治标不治本3.1 Continuous Batching 与 RadixAttention 的真实关系SGLang 的调度之所以厉害核心就是 Continuous Batching 和 RadixAttention这两个东西经常被放在一起说但很多人没搞清楚它们的边界。Continuous Batching 解决的是“迭代内怎么高效发牌”传统静态 batching 要等整个 batch 里最长的请求全部结束才能换下一批SGLang 则每步迭代都会做一次调度把已经完成的请求踢出 running batch把排队中的新请求插入空位因此不会出现“一个慢请求拖垮整个队列”的情况。RadixAttention 解决的是“重复前缀怎么省算力”它把所有请求的 KV cache 按前缀树结构组织起来新请求如果和之前的请求共享公共的 prompt 前缀就可以直接复用之前计算过的 KV cache省掉一次 prefill。这个机制对多轮对话和多模态场景特别有价值因为前后的.common_prefix 通常很长。但注意RadixAttention 的命中率直接受调度顺序影响如果调度器把前缀不同的请求混在同一个 iteration 里前缀复用的机会就变得很随机。SGLang 内部其实有相应的缓存策略但现实中你没法完全控制调度顺序所以我建议在业务层尽量把同一类模板前缀的请求路由到同一条推理链路上这样可以明显提升缓存命中率。3.2 调度器到底在权衡哪些东西我研究过不少 SGLang 调度相关的 issue 和源码发现调度器在每一步迭代里其实在做一道多目标优化题。目标一尽可能提高吞吐也就是让 GPU 每一步算得更满目标二保证等待队列里的请求不会饿死尤其是那些首 token 等待过久的请求要及时优先捞进来目标三保护显存不溢出因为 KV cache 是动态分配的一旦超了就会触发重新计算或者请求拒绝。这三个目标要同时满足最直接的约束就是max running requests和max total tokens这两个水位参数。所以你会看到单纯把--max-running-requests调大虽然让调度器敢把更多请求塞进 batch但不一定会提高整体性能反而可能让每个 iteration 的 decode 变慢。更合理的做法是同时评估--max-total-tokens约束了 running batch 里所有序列的 token 总数相当于从“显存与算力预算”的角度兜底。我们压测过一组配置对比分别设置 max total tokens 为 4096、8192、16384同样并发负载下8192 比 4096 的吞吐提升了约 40%但 16384 对比 8192 吞吐只提升了 8%TTFT 反而劣化了接近 30%。这就说明调度参数存在明显的边际效应不要盲目追求“越大越好”。3.3 业务侧配合调度的几个关键原则经验之谈调度器的调优必须跟业务请求形态结合起来不能一把梭。第一尽量设置合理的-time-limit这类请求级别超时策略把那些“无限等待”的长尾请求在调度层拦掉否则这些请求会在 waiting queue 里一直占位拖慢后面的健康请求。第二多模态场景下如果图片体积和文本长度差异很大建议直接拆成两套推理服务一套专门跑大图长文一套跑轻量短文本避免 prefill 阶段互相干扰。第三合理设置 beam search 或 sampling 的并行度SGLang 支持同一个 prompt 下生成多个候选序列这会成倍放大 running batch 的 token 总量经常是隐性超限的来源。我见过一个非常典型的坑某个服务开了 beam searchnum_beams4原本 max running requests 改成 32 之后看起来并发没变但实际上 running batch 的序列数翻了四倍KV cache 瞬间吃满然后调度器开始疯狂拒绝新请求。这类问题如果不把 batch 的“序列数维度”和“token 数维度”分开看待光盯着并发数调参永远都调不明白。4. 实操踩坑实录batch size、显存 OOM 与调度异常4.1 现象一明明显存没满请求却排队有段时间我们线上业务反馈“请求明明不多但总是超时”。进容器一看 GPU 利用率 40%显存占用 70%看起来还有余量但 waiting queue 一直堆积。后来看 SGLang 启动参数才发现罪魁祸首是--max-total-tokens设置得太小调度器认为总 token 预算已经用完了即使显存还有富余也不愿意把新请求放进 running batch。这类问题排查思路特别简单看 SGLang metrics 里 running batch 的累计 token 数和设定的 total tokens 上限是否贴边。如果是要么调高上限要么检查是不是有长序列一直不释放。4.2 现象二调大 Batch Size 后反而变慢压测某多模态模型时把--max-running-requests从 16 调到 32结果 p50 TTFT 从 300ms 涨到 900ms吞吐反而掉了 20%。复盘下来是因为模型参数量大decode 阶段本身的计算密度已经很高batch 加到 32 后单步计算时间直接翻倍新进来的请求非但没被更高效地处理还抢占了排队队列里本可以早结束的短请求的时间。这里有个关键判断标准如果模型本身的 FLOPs 已经让 GPU 处于一个接近饱和的状态那么调大 batch 不会带来吞吐收益只会增加延迟。这种场景真正该做的是降低请求内部的生成长度、压缩 prompt 长度或者用多副本横向扩展而不是堆 batch。4.3 常见问题速查表现象可能原因优先排查项显存没满但请求堆积max-total-tokens 过小看 running batch 总 token 是否贴近上限TTFT 高GPU 利用率也高prefill 阶段竞争 compute检查是否混跑大图请求考虑拆服务单请求正常并发后吞吐不涨cuda graph 最大 batch 太小查看 cuda graph capture 是否频繁回退长请求一多后面全部超时max-running-requests 过大降低 running requests或者加请求级超时多轮对话第二次请求很慢前缀缓存命中率低检查 prompt 模板是否保持一致开头偶发 OOM 但显存看着还有余量KV cache 瞬时峰值超预算开启显存预留和动态缓存回收策略4.4 一组可参考的初始配置我们没有放之四海皆准的默认参数下面是基于英伟达 L20 或 A800 这类 48GB 显存、7B 到 8B 规模多模态模型在交互式场景下的一个初始组合比较保守但至少能稳定跑起来python -m sglang.launch_server \ --model-path your-omni-model-path \ --host 0.0.0.0 \ --port 8000 \ --max-running-requests 32 \ --max-total-tokens 4096 \ --schedule-policy lpm \ --cuda-graph-max-batch-size 16 \ --mem-fraction-static 0.8说明一下这几个参数为什么这么设。max-running-requests设 32避免请求太多导致 decode 步长过长max-total-tokens设 4096相当于平均每条 in-flight 请求只允许 128 个 token实际批量更接近内存受限的核心而不是并发受限的核心schedule-policy lpm就是 shortest-job-first 思路优先处理长度较短的请求从而让 TTFT 的 p95 下降。cuda-graph-max-batch-size设 16 是为了防止 graph 捕获的形状超过内核预留范围如果 batch 超过 16框架会回退到 eager 模式性能会掉。线上实测下来这套配置对混合短文本请求更友好但如果你的业务长度普遍超过 2048 token记得把max-total-tokens相应放大并配套观察显存水位。还有一个小细节SGLang 提供了一个非常实用的 metrics 接口在启动的/metrics路径上直接暴露了running_waiting_requests、total_tokens、num_prefill等等我建议所有排障都先看一眼这套指标再决定动哪个参数比对着日志猜高效得多。我们在把压测和线上监控都接上这套指标之后之前那种“batch size 争论基本不谈了现在都是先拉曲线看 running batch 的 token 水位再决定调谁。”5. 还有几个值得延伸的点最后分享一个我们最近在考虑的方向。Batch Size 的争论说到底是对“资源利用率”和“服务质量”二者权重分配的分歧。对一个以对话交互为主的服务延迟权重就应该高一些可以把 max total tokens 压得保守一点对一个离线批处理场景吞吐权重高可以把 batch 拉大甚至自定义调度策略优先填满 GPU。SGLang Omni 的好处在于这些策略都在框架内可以通过参数调整不用改业务代码所以值得多花点时间用一套科学的方法去把不同负载模型都压一遍。我个人在实际操作中还有个体会不要迷信“默认参数”也不要迷信“别人调好的参数”。同一套 SGLang 配置在不同模型、不同量化格式、不同图片输入比例下最优值能差出一倍。每换一次模型、每改一次数据分布都值得重新做一轮系统测试。把上面讲的四类指标、三类场景、一组调度参数排摸清楚你会发现所谓的 Batch Size 争论会自然消散因为它已经变成了一个可以从数据中找答案的问题而不是凭直觉投票的问题。
网站建设高端定制企业官网
RELATED

相关资讯

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

较早相关资讯

最新相关资讯

MongoDB复制集原理与实战:高可用、oplog同步及故障切换详解 2026/9/30 11:48:37

MongoDB复制集原理与实战:高可用、oplog同步及故障切换详解

1. 复制集到底是什么,为什么要用它 我刚接触MongoDB那会儿,最常被问的问题就是:MongoDB到底能不能扛住生产环境?问的人多半在单机部署上栽过跟头——mongod进程一挂,业务直接停摆,数据恢复全靠备份&#xf…

阅读更多 →
单链表核心操作全解析:带头结点、插入逆置与C/Python实现 2026/9/30 11:48:37

单链表核心操作全解析:带头结点、插入逆置与C/Python实现

链表是所有学数据结构的人绕不过去的坎。你搜"链表",大概率是正在补作业、准备考研复试、或者刷LeetCode刷到怀疑人生。这玩意儿抽象、指针绕、边界条件多,尤其是"在指定位置插入建立单链表"、"不带头结点的单链表"这类操…

阅读更多 →
Linux PTP 高精度时间同步实战:从原理到 ptp4l 调优与避坑 2026/9/30 11:48:37

Linux PTP 高精度时间同步实战:从原理到 ptp4l 调优与避坑

简介:这是一份面向网络工程师、嵌入式开发者及时间同步技术学习者的PTP入门与实操资料,围绕高精度时间同步协议展开,帮助读者理解PTP相对NTP在精度上的差异,并掌握在Linux环境下搭建测试环境的方法。资源包内共1个docx文档&#x…

阅读更多 →
小程序制作平台有哪些?小程序上线的四个必经环节 2026/9/30 11:48:30

小程序制作平台有哪些?小程序上线的四个必经环节

小程序运行在微信内部,不用下载安装,用户从会话、公众号菜单、搜一搜或者分享卡片就能直接打开。它没有独立的应用商店,也不走应用市场的分发逻辑,而是依附微信这套账号和支付体系存在。这种形态决定了它的上线流程和独立 App 完全…

阅读更多 →
OpenClaw+硅基流动API:自建AI Agent终端助手部署指南 2026/9/30 11:48:24

OpenClaw+硅基流动API:自建AI Agent终端助手部署指南

OpenClaw、硅基流动API、邀请码CUdmAtEa,这三个关键词是我最近几天全部折腾的缩影。简单说,OpenClaw是一个运行在终端里的开源AI Agent,它不是又一个聊天机器人,而是能实际读写文件、执行命令、调用工具、完成多步骤任务的命令行助…

阅读更多 →
Redis集群模式全解析:从原理到搭建踩坑指南 2026/9/30 11:48:24

Redis集群模式全解析:从原理到搭建踩坑指南

熟悉Redis的朋友应该都有同感:单机版用起来确实爽,性能高、API简单,但流量一上来、数据一多,单机的那点内存和单线程吞吐就成了墙。Redis集群模式正是用来撞开这堵墙的官方方案,它不仅解决了容量和吞吐的横向扩展问题&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

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

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