大模型在线回答问题时,每条请求占用 GPU 的时间并不一样。有人只要一句摘要,有人让模型写完整方案。把这些请求一次凑成固定批次,短回答已经结束,长回答还在逐字生成,空出来的位置就会等着整批结束。连续批处理专门处理这段空等。

我核对了 Hugging Face 的连续批处理说明和 TGI 服务参数。它的做法很直白。调度器在每一步生成后重新看批次,完成的请求离开,队列里的新请求只要放得下就加入。GPU 因而能持续处理多条序列,服务在高并发时通常可以多完成一些请求。

它和离线批量推理有何区别

离线批量推理适合一批已经准备好的任务,例如夜里给十万条商品补标签。提交以后可以等整批完成,单条结果晚几分钟通常没有关系。连续批处理面对的是不断到来的在线请求,每个人都在等第一个字和后续输出,调度器要一边收新请求,一边照顾已经生成到一半的请求。

生成还分两个阶段。新请求先做 prefill,把整段输入算一遍并建立 KV cache。随后进入 decode,每轮通常只新增一个 Token。把新请求插进正在运行的批次,调度器需要安排一次较重的 prefill。插得太频繁,老请求的输出会卡顿。一直不插,新请求又会在队列里等很久。

TGI 的 max-waiting-tokens 就在管这件事。官方文档提醒,数值太小会让等待请求频繁抢走计算,正在生成的请求会被拖慢。数值太大,空闲服务器上两秒能完成的请求,在繁忙时可能先排十几秒。这里没有一个适合所有业务的固定答案,客服短问答和长文生成所能接受的等待时间本来就不同。

批次真正受令牌预算限制

在线生成很难只用请求条数管理批次。一条请求可能只有一百个 Token,也可能带着几千个 Token 的文档。TGI 用批次总 Token 上限控制容量。官方举例中,同样一千个 Token 的预算,可以装十条各一百 Token 的请求,也可能只能装下一条一千 Token 的请求。

KV cache 也会占显存,而且每条请求长度不同。vLLM 论文指出,缓存碎片和预留过多会减少实际能放进批次的请求数。PagedAttention 把缓存拆成页来分配,连续批处理因此更容易利用零散显存。两项技术常一起出现,解决的问题仍有区别。前者管内存怎样放,后者管请求何时进出。

上线以后,我会同时看吞吐量、队列时长和首字等待。TGI 已提供当前批大小、队列时长、请求总时长及每个 Token 用时等指标。每秒处理的 Token 增加了,若排在尾部的请求明显更慢,用户体验仍然可能变差。先用真实的长短请求比例压测,再调 Token 预算和等待阈值,连续批处理才能真正省下 GPU 的空转时间。

参考资料