批量推理实战:离线任务怎么跑得更快更省
批量推理实战:离线任务怎么跑得更快更省
LLM 服务有两类负载:一类是聊天这种要求低延迟的在线请求,另一类是「把一万篇文档分类」这种不关心单条延迟、只关心总时间的离线任务。用同一套在线端点逐个串行调用后者,既慢又贵。批量推理就是把这类任务组织成吞吐优先的流水线——本文讲清怎么做。
30 秒版本
先判断负载类型:用户在等结果走在线端点,不在等就组织成批量队列。具体模型 ID 以 /models 目录实时为准,价格只链 /pricing。
吞吐与延迟是两种目标
| 维度 | 在线推理 | 批量推理 |
|---|---|---|
| 首要指标 | 首 token 延迟 | 单位时间处理量 |
| 并发 | 低(保证单请求速度) | 高(填满算力) |
| 失败处理 | 立即返回错误 | 收集后重试 |
| 计费关注 | 单请求成本 | 总量成本 |
判断标准很简单:用户是否在等这一条结果。在等,就走在线;不在等,就该进批量队列。
连续批处理为什么有效
传统批处理要等整批都完成才释放资源,慢请求拖住整批。连续批处理(continuous batching)允许某个请求生成完毕后立刻把它的槽位让给排队的新请求,GPU 几乎不空转。vLLM、SGLang 等推理框架都实现了这一机制。
对调用方来说启示是:把任务稳定地喂给服务端比「一次发一大批然后等待」更高效。保持队列有货,服务端才能持续填满批次。
本地批量推理的显存约束
并发数不是越高越好:KV cache 随并发线性增长。单机批量任务的并发上限 ≈ (显存 − 权重 − 开销) ÷ 单请求 KV cache。超过就会 OOM 或触发 swap,吞吐反而崩。
组织一个离线批任务
1. 输入切片与去重。 把大任务拆成独立单元,先去重(相同输入不必重复算)。
2. 固定参数与提示词。 整批用同一套温度、max_tokens、系统提示,保证结果可比。
3. 并发控制 + 限流重试。 用有上限的并发(如 4-8),429/503 用指数退避,失败单元单独记录而不是中断整批。
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from openai import OpenAI
client = OpenAI(api_key="你的APIKey", base_url="https://exaiv.com/api/ai/v1")
def run_one(item, retries=4):
for attempt in range(retries):
try:
return client.chat.completions.create(
model="kimi-k3", # 以 /models 目录实时为准
messages=[{"role": "user", "content": item}],
temperature=0,
).choices[0].message.content
except Exception:
if attempt == retries - 1:
raise
time.sleep(2 ** attempt)
results, failures = {}, []
with ThreadPoolExecutor(max_workers=6) as pool:
futures = {pool.submit(run_one, it): i for i, it in enumerate(items)}
for fut in as_completed(futures):
i = futures[fut]
try:
results[i] = fut.result()
except Exception as e:
failures.append((i, str(e)))
4. 结果落盘与断点续跑。 每条结果带输入 id 写文件;中断后跳过已完成 id 继续,不要从头重跑。
批量任务常见坑
上下文超长:长文档先切分,不要整本丢进去(见上下文工程一文)。
输出解析失败:结构化任务要求 JSON 输出,解析失败的单条单独重试,不要污染整批。
重复计费:重试时确认没有产生重复成功结果;用输入 id 做幂等键。
静默质量漂移:批量任务更容易「跑完但效果变了」。抽 5-10% 结果人工抽查,或用评测集做回归(见模型评测方法论)。
一个完整例子:一万篇文档分类
把流程串起来:把 10000 篇文档分到 8 个类目。
- 预处理:每篇截取前 2000 字符,去重(用内容哈希),得到 9400 个独立单元。
- 固定配置:温度 0、结构化输出要求 JSON、同一条系统提示。
- 并发 6 跑批:用上面的骨架,结果带输入 id 落盘。
- 失败处理:429/503 单独重试;解析失败的单条重跑一次,不污染整批。
- 断点续跑:中途网络中断,重启时跳过已落盘 id。
- 质量抽查:随机抽 500 条(约 5%)人工核对类目,另用固定评测集跑一次回归。
整个过程里最容易踩的坑是「一次性开 64 并发」——云端会持续 429,本地会 KV cache 撑爆显存。从 4-8 起步,按反馈调整,才是稳定吞吐的做法。
整个过程的成本主要是 token 消耗与重试次数,两者都随并发和上下文长度变化。跑完后记录四个数字:每分钟完成量、失败重试率、单条平均上下文长度、以及最慢的那批单元耗时。这四个数字能告诉你下一批该调并发还是该切分文档,而不是靠猜。批量任务跑完不等于跑对,配合固定评测集做回归,才能确认输出质量没有因为参数或模型版本变化而静默漂移。
常见问题
什么任务适合批量?
并发设多少?
连续批处理需要我实现吗?
批量会更便宜吗?
跑一半失败了怎么办?
怎么保证批量结果质量?
相关阅读
- RAG 知识库搭建实战 — 检索与上下文的配合
- 模型评测方法论 — 用固定评测集做回归
- Qwen 成本优化实战 — token 消耗控制
- 显存计算实战 — 并发的显存上限
- 定价 — 云端实时价格