深夜十一点,街角的小餐车前排着长队。阿杰的餐车只有一个灶眼——就像资源受限的手机、耳机、智能摄像头,算力、内存、电全都紧张。更让他肉疼的是,只要开火就有损耗:烧油、预热、备料,这些固定成本不管做一份还是做四份都躲不掉。客人催得急,他不敢让人久等;可他又琢磨:要是把几份订单凑在同一锅里炒,摊到每份单上的“开火成本”不就小了吗?这正是端侧推理里最要紧的学问——批处理。云端大厨房锅大油足,一锅能出几十份;餐车只能小锅小灶,还得学会把相似订单攒到同一锅(请求合并),再想办法让每一锅更省(算子融合、动态批量)。这篇文章就带你用一只灶眼的智慧,看懂端侧设备如何优雅接住汹涌而来的推理请求。
掌握端侧批处理与请求合并优化,是端侧推理工程师把大模型真正落地到手机、IoT 等受限设备的核心技能,直接决定端侧服务的吞吐、延迟与功耗表现,也是实战面试中的高频考点。
可视化演示
交互展示动态批调度、请求合并和小batch高效推理等端侧批处理优化策略,理解如何在资源受限设备上提升推理效率。
本节配有交互动效(进入视口后自动加载,约 3KB)。
动态批调度策略 根据实时请求负载动态调整batch大小 平衡吞吐量与延迟的自适应调度 动态Batch 资源受限设备也能高效处理并发请求
端云批处理差异
阿杰的餐车只有一个灶,中央厨房却有二十口锅。中央厨房的做法是:积压两三百份订单后一次性开火,每份订单分摊的燃料成本微乎其微;餐车要是照抄,别说锅放不下,光等两三百人到齐,客人早就走光了。小锅永远不可能用大锅的玩法。
把多个推理请求放进同一次前向计算,就叫批处理(Batch),一次放入的数量叫批量(batch size)。云端 GPU 显存大、功耗预算足,批量动辄 64、128;端侧手机、摄像头只有几 GB 内存共享给系统和各种 App,批量上限常常只有 1 到 8。这不是不想用大批量,而是“锅”真的只有这么大。
端侧与云端的根本差异,藏在每次推理的固定开销里。每次开火都有所谓的启动成本:加载算子、搬运权重、预留激活内存。批量越大,这笔成本被摊得越薄,云端的做法是用大 B 把均摊成本压到可以忽略;端侧则必须面对“小 B 摊不动”的残酷现实,把每一分固定开销都用在刀刃上。
1. 固定开销$C_{fixed}$:每次推理启动都要付出的一次性成本,与批量无关,包括加载算子、搬运权重、初始化内存等。
2. 批量均摊:把固定成本除以批量 B,得到每个请求分到的开火份额。
3. 单请求增量$C_{per}$:每个请求本身消耗的计算时间,随模型大小和输入长度增长,与批量关系不大。
4. 汇总公式:单请求均摊耗时等于平摊后的固定开销,再加上每个请求自身的计算量。
所以阿杰从不贪心:一锅四份,刚刚好。端侧工程师的原则同样是“小锅出精品”——与其盲目追求大批量,不如先把固定开销的账算清楚,等学会了攒单和融合,小锅也能转出大厨房的效率。
请求合并机制
阿杰最头疼的是订单稀稀拉拉地来:这个客人刚走,下一个五分钟后才到。若来一份炒一份,开火成本高得吓人。他学聪明了:立一块“正在备餐,30 秒内继续接单”的牌子,把同一时段内的订单凑成一锅炒,既不让灶台频繁空烧,也不让客人等太久。
这就是请求合并(Request Batching):服务层不立刻推理每个请求,而是把它们放进等待队列,攒够一定数量或等满一个时间窗口再统一处理。端侧引擎常用“窗口 + 上限 + 超时”三件套:窗口决定攒单时长(如 30ms),上限决定一锅最多几份(如 4),超时兜底保证窗口到了哪怕没凑满也必须开火。三者共同构成一个最朴素的队列调度模型。
合并的收益可以用到达速率与窗口估算。请求到达越快、窗口越长,攒到的批量越大,均摊越划算;但窗口过长又会拖长每个请求的等待。因此窗口、上限、超时三个旋钮必须一起调,让灶台“恰好接住”客流,而不是等着等着把客人都等跑了。
1. 到达速率$\lambda$:单位时间内到达合并队列的平均请求数,如每秒 100 个请求。
2. 窗口时长$W$:引擎愿意等待合并的最长时间,也就是“继续接单”的牌子挂多久。
3. 期望批量:窗口内平均攒到的请求数,等于到达速率乘以窗口时长。
4. 上限约束:实际批量还要被最大批量钳制,超过锅的容量就得分锅。
就像餐车的“继续接单”牌既不让客人干等太久、又让灶台尽量少空烧,端侧引擎通过窗口与超时的配合,在“等得起”与“多接单”之间找到了平衡点。
小批量高效推理
就算一锅只有四份,阿杰还是觉得亏:每次开火都要先烧油预热。于是他干脆把洗菜、切菜、炒菜合并成一道流水工序——同一个灶台,边切边炒。省掉一次“重新点火”,就省下一大笔固定成本,出餐速度肉眼可见地快了。
端侧推理里的“重新点火”就是算子启动开销:每执行一个算子,都要分配内存、搬运数据、启动计算内核。当 batch=1 时,固定开销可能占总耗时的 60% 以上,锅几乎都在“烧油”而不是“炒菜”。改善方向有两个:一是凑小批量把开销摊薄,二是算子融合——把“卷积 + 归一化 + 激活”这类连续算子合并成一个复合算子,一次启动完成多步计算,大幅减少内存读写次数。
此外,端侧还常配合内存复用:让多个算子在同一个暂存区上接力,避免反复申请、释放内存。小批量、算子融合、内存复用三者叠加,即使每个 batch 只有 2 到 4 个请求,也能把算力利用率拉得很高。
1. 总耗时构成:一次推理总时长由固定启动开销加上批量计算量组成。
2. 开销占比:固定开销在总时长中的比例,B 越小占比越高。
3. 最坏情形:当 B=1 时占比达到最大,逐条推理最浪费。
4. 融合之后:多个算子合并后启动开销从 $C_{fixed}$ 降到 $C_{fused}$,远小于原值。
阿杰的灶不再频繁“冷启动”,端侧模型也不再为每个算子支付启动税。记住:端侧优化的重点不是把锅换大,而是让每一次开火都物尽其用。
性能指标评估
阿杰开始给自己记账:一分钟出了几份餐(吞吐量),客人平均等了几分钟(延迟),灶台有多少时间在真正炒菜(利用率)。没有账本,改火候全凭拍脑袋,生意越做越糊涂。记账之后,他才明白哪些调整真的有用。
端侧批处理的核心指标同样是这三样:吞吐量指单位时间完成的请求数,端到端延迟指请求从进入排队到返回结果的全程耗时,资源利用率看算力、内存和电耗的忙碌比例。其中吞吐和延迟是一对天生的冤家——批量 B 增大,吞吐上升、均摊开销下降,但每个请求要排队等更久,峰值内存也更高。
从公式看,端到端延迟等于排队等待加上均摊计算时间;吞吐等于批量除以每批耗时。调参的实质就是:在延迟红线内把吞吐推到最高。比如产品要求最大 100ms 延迟,那窗口就得定在 20ms 以下,给计算留 80ms 的余量。
1. 等待延迟$L_{wait}$:请求在合并队列中等待的时间,平均约为窗口时长的一半。
2. 均摊计算:合并后每个请求分到的固定开销部分。
3. 端到端延迟:总延迟等于等待时间加均摊计算时间加单请求计算量。
4. 吞吐量:单位时间完成的请求数,等于批量除以每批总耗时,B 增大时整体上先升后稳。
阿杰的账本让“何时开火、一锅几份”有了依据;端侧工程师也依靠这三项指标,把 batch 调优从玄学变成了一门可以复盘的工程学。
端侧调优实践
阿杰后来发现:高峰时段客人多,一锅四份不够用;深夜客人少,四份又凑不齐。于是他给灶台装了档位——人多时一锅六份,人少时一锅两份,火候跟着订单走。灵活,才是小锅的生存之道。
这就是动态批大小(dynamic batching):根据实时排队长度、内存余量、延迟水位,动态调整批量与合并窗口。排队多且内存够,就加大批量;排队少,就缩短窗口、尽快开火。实现上要求模型支持 1 到 N 的任意批量,用动态 batch 维度而非固定 padding,避免无效计算浪费。请求还要有优先级:实时交互请求排前面,优先被包进下一锅;后台批量任务排后面,有空位再补。
端侧调优离不开性能剖析工具:profiling 能展示每个算子的耗时与内存峰值,帮工程师找出“烧油最狠”的环节。一套完整的调参流程是:先定延迟预算,再选初始批量与窗口,然后实测吞吐与内存,接着调旋钮,最后回归验证。每一步都要拿数据说话,循环迭代直到达标。
1. 可用内存$M_{avail}$:端侧设备当前能分配给推理的内存余量。
2. 单位需求:每个 batch 单位需要的内存,包含模型权重和激活值两部分。
3. 向下取整:用可用内存除以单位需求后向下取整,保证绝不超内存。
4. 完整上限公式:实际批量不得超过这个内存上限,动态调整时先算它再定档位。
火候跟着订单走,档次跟着现场调——端侧引擎的动态调度,让每一分算力都花在当下最需要的请求上,而不是被一套死参数绑住手脚。
部署落地场景
阿杰的餐车偶尔也会遇到“神仙单”:一单要十份,小锅实在接不住。他把单子转给隔壁中央厨房,代价是多几分钟配送时间。于是定下规矩:本地能做的本地做,本地做不动的再上云。这条规矩,也是端云协同推理的雏形。
端侧部署最常见的场景是端云协同:模型裁剪后跑在本地,轻量请求由本地小批量处理;重活、大批量请求通过网络抛给云端大厨房。选择的依据就是端到端延迟对比:本地延迟比“云加网络”延迟小就走本地,否则上云。弱网环境下端侧优先,强网环境下云端分担,两头都吃满。
另一个高频场景是流式合并:实时字幕、语音转写会源源不断产生碎片请求,端侧引擎按固定时间片把同一时间片内的碎片聚成微型 batch,逐段推理,既保持流式的低延迟感,又享受合并红利。多 App 并发时也一样,不同应用的请求可以在同一个模型服务里汇入同一个队列,统一合并推理,避免每个 App 各开一灶。
1. 纯端延迟$L_{edge}$:请求在本地完成批处理等待与计算的总耗时。
2. 云侧方案$L_{cloud} + L_{net}$:请求上传到云端推理,再加上网络往返的时间。
3. 择优选择:实际延迟取本地与云端两条路中较小者。
小锅有自己的边界,也有自己的智慧。端侧批处理不是云端的缩小版,而是一套围绕受限资源的完整生存哲学:能合并就合并,能本地就本地,剩下的交给云端接力。
AI 应用场景
手机多应用并发推理
阶段:推理部署与在线服务优化
解决的问题:手机同时运行实时字幕、拍照翻译、语音助手时,多个应用各自排队逐个推理,每次推理都支付一次固定启动开销,延迟和功耗双双超标。AI 通过统一请求队列与合并窗口,把多个应用的推理请求凑成同一批,一次前向计算服务多个应用。
具体案例:一款中端手机本地部署 2B 对话模型,同时到达 3 个语音转写和 2 个字幕请求,端侧引擎在 40ms 窗口内合并成 batch=5 统一推理,相比逐个处理吞吐提升约 2.3 倍,功耗下降约 30%。
智能家居网关流式批处理
阶段:边缘数据流接入与推理框架
解决的问题:智能网关持续接收多路传感器和摄像头数据,请求碎片化严重,逐条推理浪费严重。AI 按固定时间片把同一时刻的多路数据聚成微型 batch,流式地逐段合并推理,在低延迟下保持高吞吐。
具体案例:家庭网关对 4 路摄像头画面做人体检测,把每 50ms 时间片内的 4 帧图像合并成一个 batch 推理,帧率从 12fps 提升到 20fps,且每帧延迟仍控制在 150ms 以内。
端云协同弹性推理
阶段:混合推理调度与资源管理
解决的问题:单一端侧或云端方案都无法兼顾低延迟和大算力。AI 按端到端延迟公式动态分流:本地排队短走本地小批量,本地排队过长或任务过重则走云端大批量,网络恢复后回切本地。
具体案例:车载语音助手在高速弱网下切换到本地小批量推理,保证 200ms 内响应;进入停车场有 Wi-Fi 后,复杂的语义理解请求自动上云大批量处理,本地只保留轻量唤醒。
Python 演示
import time FIXED_OVERHEAD = 0.02 # 每次推理启动固定开销 20msPER_REQUEST = 0.01 # 每个请求增量计算 10msBATCH_SIZE = 4 def infer_batch(batch): # 模拟一次端侧批量推理:固定开销 + 随批量增长的计算量 time.sleep(FIXED_OVERHEAD + len(batch) * PER_REQUEST) return [x + 1 for x in batch] def run_standalone(requests): start = time.time() for req in requests: infer_batch([req]) return time.time() - start def run_merged(requests, max_batch=BATCH_SIZE): start = time.time() for i in range(0, len(requests), max_batch): infer_batch(requests[i:i + max_batch]) return time.time() - start def main(): requests = list(range(9)) t1 = run_standalone(requests) t2 = run_merged(requests) print("Standalone time: %.3f s" % t1) print("Merged batch time: %.3f s" % t2) print("Group sizes:", [len(requests[i:i + BATCH_SIZE]) for i in range(0, len(requests), BATCH_SIZE)]) print("Speedup: %.2f x" % (t1 / t2)) if __name__ == "__main__": main()
FIXED_OVERHEAD = 0.02模拟每次推理开火的固定成本:加载算子、搬运权重,与批量无关。time.sleep(FIXED_OVERHEAD + len(batch) * PER_REQUEST)一次推理耗时 = 固定开销 + 批量 × 单请求增量,正是均摊公式的核心。infer_batch([req])逐条推理时每个请求单独开一次火,固定开销被重复支付。range(0, len(requests), max_batch)按最大批量把请求切成若干组,模拟请求合并的凑批过程。requests[i:i + max_batch]一组请求合并为同一个 batch 输入,一次前向计算完成多个请求。(t1 / t2)计算逐条处理与合并处理的总耗时比,即加速比,代码直接印证理论值。
常见误区
误区“我在云端把 batch 调到 64 效果很好,端侧也照做,把窗口拉长、批量拉大,吞吐一定更高。”
正解端侧有内存墙和功耗墙。批量增大时激活内存线性上涨,稍微越界就会触发内存交换甚至 OOM,延迟瞬间飙升,功耗也会超过设备发热红线。正确做法是先用内存上限公式算出安全批量,再在延迟预算内选择最大可行值。就像餐车灶台就那么大,强行架一口六份的大锅,火不仅烧不透,还会把台面压塌。
为何常见:云端“大 batch 高吞吐”的经验太深入人心,忽略了端侧内存和散热是硬约束。
误区“合并要等窗口,明显拖慢响应;低延迟就该来一个请求立刻推理一个,绝不等待。”
正解batch=1 时固定启动开销占比极高,单请求绝对耗时反而更大。窗口内等待几十毫秒,换来固定开销被摊薄,单请求平均时间可能下降 30%~50%。这个道理就像外卖小哥等 3 分钟凑齐同楼两单,比立刻出发跑两趟快得多。真正的低延迟是在延迟预算内做最优合并,而不是拒绝合并。
为何常见:直觉上“等待”等于“变慢”,忽略了固定开销才是小请求延迟的大头。
误区“云端用 30ms 窗口、batch=64 效果不错,端侧直接用同一套参数,省事又可靠。”
正解端侧的算力、内存、算子库实现与云端完全不同,相同的 batch 在端侧可能直接 OOM 或延迟爆表。不同模型、不同芯片、甚至不同线程数下,最优批量都不同,必须基于 profiling 实测重新调参。好比大锅炖肉的配方不能直接套到小炒锅上,火候、油量、时间都得重新试过。
为何常见:参数直觉来自云端经验,缺乏端侧 profiling 数据的支撑,容易想当然。
误区“定好一个 batch 大小和窗口就一劳永逸,动态调整太复杂,没必要。”
正解请求到达率随时间剧烈波动:高峰期固定小批量导致吞吐不足,低谷期固定大批量导致等待浪费。动态批大小按队列长度和内存余量实时调整,能同时压住延迟和吞吐两个指标。就像餐车高峰换大锅、低谷换小锅,灶台利用率才最高,现代端侧引擎普遍内置动态批量支持。
为何常见:动态策略实现复杂,静态配置看起来“可控”,却忽略了负载波动才是常态。
面试题
Q1. 端侧设备推理时,相比云端更适合采用哪种批处理策略?
- A.尽量增大 batch 到极限
- B.小 batch 配合算子融合← 正确答案
- C.完全放弃 batch 处理
- D.batch 与效率毫无关系
答案:B。端侧资源受限,大批量容易触发内存溢出,最优策略是小批量并配合算子融合降低固定开销。放弃 batch 或认为 batch 无关都是错误的,批处理恰恰是端侧优化的核心手段。
Q2. 请求合并策略中 max_batch 上限的主要作用?
- A.提高模型推理的精度
- B.加密请求保护隐私数据
- C.控制延迟与峰值内存← 正确答案
- D.减少端侧请求的总数
答案:C。max_batch 限制单次批处理的最大请求数,防止等待过久和内存超限,是延迟与内存的保护闸。它不改变精度、不负责加密,也减少不了到达的请求总量。
Q3. 时间窗口合并策略中,窗口设置过短会导致什么?
- A.批次凑不满固定开销难均摊← 正确答案
- B.请求队列发生内存溢出
- C.模型推理精度明显下降
- D.请求在传输途中丢失
答案:A。窗口太短,攒单时间不足,批次经常以 batch=1 或 2 开火,固定开销无法均摊,效率反而更低。窗口过短不会直接导致 OOM,也不会影响精度或丢包。
Q4. 端侧 batch=1 逐个推理低效的根本原因?
- A.端侧模型体积实在太小
- B.每次启动固定开销占比高← 正确答案
- C.端侧请求数量不够多
- D.端侧没有云端 GPU
答案:B。batch=1 时每次推理都要支付一次完整启动开销,固定开销在总耗时中占比极高。模型大小、请求数量和有无 GPU 都不是“逐个处理低效”的根本原因。
Q5. 下列哪项是端侧批处理与云端批处理的本质区别?
- A.端侧批量总是比云端更快
- B.云端无法支持批量推理
- C.端侧无需考虑延迟问题
- D.端侧资源受限批量更小← 正确答案
答案:D。端侧内存、功耗、算力都受限,批量上限远小于云端。端侧批量并不总是更快,云端完全支持批量,延迟问题反而在端侧更敏感。
Q6. 关于批大小 B 与单请求均摊耗时 T_avg 的关系?
- A.T_avg 随 B 增大线性上升
- B.T_avg 与 B 完全无关
- C.T_avg 随 B 增大均摊递减← 正确答案
- D.T_avg 随 B 指数爆炸
答案:C。T_avg = C_fixed / B + C_per,B 增大时固定开销被摊薄,均摊耗时递减并趋近 C_per。不会线性上升、更不会指数爆炸,也绝非与 B 无关。
Q7. 算子融合优化主要减少的端侧开销是?
- A.每次启动算子的固定成本← 正确答案
- B.模型神经网络的参数量
- C.训练阶段数据的数量
- D.请求合并等待的时间
答案:A。算子融合把多个连续算子合并为一个,降低算子启动和内存搬运的固定成本。它不改变参数量、不减少训练数据,也不直接影响请求等待时间。
Q8. 端侧在线批处理中,超时兜底机制的核心作用?
- A.防止请求无限期等待凑批← 正确答案
- B.提高推理结果的精度
- C.减少模型占用的内存
- D.提高训练收敛的速度
答案:A。当请求稀疏、窗口内凑不满 max_batch 时,超时兜底保证到点就开火,避免请求无休止排队。它服务于在线延迟控制,与精度、内存占用、训练收敛无关。
Q9. 评估端侧批处理优化效果时,应关注哪些指标?
- A.端到端推理延迟← 正确答案
- B.单位时间请求吞吐量← 正确答案
- C.峰值内存占用大小← 正确答案
- D.离线训练数据总量
答案:ABC。批处理调优的核心指标是延迟、吞吐和内存三类,它们共同决定端侧体验。训练数据总量是模型训练阶段的概念,与线上批处理评估无关,属于干扰项。
Q10. 下列关于端侧请求合并机制的描述正确的有哪些?
- A.窗口内收集请求凑批← 正确答案
- B.到达 max_batch 即触发← 正确答案
- C.超时未满也会触发← 正确答案
- D.合并必然增加总延迟
答案:ABC。请求合并由三件套驱动:窗口收集、数量触发、超时兜底,三者配合。合并通过均摊固定开销反而降低单请求平均延迟,“必然增加总延迟”的说法不成立,D 错在把排队等待等同于整体变慢。