Embedding 批量请求的长度和失败重试设计
来源:17golang原创
时间:2026-10-17 00:02:55 383浏览 收藏
Embedding 批量请求最容易出问题的地方,不是“批量越大越快”这么简单,而是三类限制经常被混在一起:一次放多少条、每条允许多长、整个请求体能有多大。稳妥的设计是先用 token 预算切批,再把超时、限流和服务暂不可用与输入过长、鉴权错误分开处理。这样即使一批只成功了一部分,也能按原始索引补偿,不会把向量写错位置。
官方地址:https://huggingface.co/docs/huggingface_hub/main/package_reference/inference_client
把 batch_size 当成并发参数,把单条 token 上限当成输入参数,把请求体预算当成传输参数;重试只覆盖可恢复故障,并且每次重试都携带原始索引。
先把三个长度限制拆开
以 Hugging Face 的 InferenceClient.feature_extraction 为例,接口可以接收单条文本,也可以接收多条文本列表;具体模型或推理服务还可能有自己的最大输入长度和请求大小。文档里的 truncate 只说明服务支持截断时可以控制输入处理,并不等于所有模型都有相同的 token 上限。
因此,批处理器至少要维护三个变量:
max_items:一次最多放多少条,用来控制响应数量与单次内存。max_tokens:单条文本允许的 token 预算,超出时截断、拆分或进入人工处理队列。max_request_tokens:整批估算 token 总量,用来避免短文本数量过多或长文本混在一起。

这三个限制不要写成一个“万能 batch_size”。例如固定 32 条,在短句场景可能浪费吞吐;同样的 32 条换成长文,又可能让请求体过大。更可靠的切批条件是“达到条数上限或达到 token 预算,满足任一条件就结束当前批次”。
按 token 预算组装批次
如果服务 SDK 能返回 tokenizer 的精确计数,优先使用它;没有 tokenizer 时只能使用保守估算,并把最终的超限错误当成输入失败处理。下面的示例刻意把估算器作为依赖注入,便于替换成真实模型 tokenizer:
from dataclasses import dataclass
@dataclass
class TextItem:
# 原始序号用于把向量和输入稳定配对。
index: int
text: str
def build_batches(items, estimate_tokens, max_items=16, max_request_tokens=4096):
# 任意一条文本都不能单独超过整批预算。
batch, used_tokens = [], 0
for item in items:
tokens = estimate_tokens(item.text)
if tokens > max_request_tokens:
# 交给上层做截断或失败记录,避免静默丢失语义。
raise ValueError(f"item {item.index} exceeds token budget")
reaches_limit = batch and (
len(batch) >= max_items or used_tokens + tokens > max_request_tokens
)
if reaches_limit:
# 先产出已装满的批次,再重新计算当前条目。
yield batch
batch, used_tokens = [], 0
batch.append(item)
used_tokens += tokens
if batch:
# 最后一批可能不足 max_items,但仍然需要发送。
yield batch
实际项目中,estimate_tokens 应与目标 Embedding 模型使用同一套分词规则;字符数只能作为粗略保护。对超长输入,不要在批处理器里悄悄截断后继续写入生产向量,至少要记录截断方向和原始长度,否则排查召回变化时没有依据。
把错误分成可重试和不可重试
重试策略要看错误是否会因为等待而改变。超时、限流、服务暂不可用通常可以有限重试;输入过长、参数不合法、鉴权失败则应立即进入失败队列。Hugging Face 文档列出的异常包括 InferenceTimeoutError 和非 503 的 HTTP 错误,接入层应把 SDK 异常映射成自己的错误类别,而不是对所有异常统一重试。
指数退避的重点不是“多试几次”,而是给服务恢复时间,并给每个批次设置上限。下面的代码使用伪造的 embed 调用表示供应商 SDK,展示重试边界和索引保留:
import random
import time
RETRYABLE = {"timeout", "rate_limit", "service_unavailable"}
def embed_with_retry(batch, embed, max_attempts=3, base_delay=1.0):
# batch 中的 index 始终跟随文本,不能用返回顺序猜测归属。
for attempt in range(max_attempts):
try:
vectors = embed([item.text for item in batch])
if len(vectors) != len(batch):
# 数量不一致不能写库,交给失败队列等待人工处理。
raise RuntimeError("embedding result count mismatch")
return [(item.index, vector) for item, vector in zip(batch, vectors)]
except Exception as exc:
kind = classify_embedding_error(exc)
if kind not in RETRYABLE or attempt == max_attempts - 1:
# 永久失败和耗尽次数的临时失败都保留原始批次。
raise EmbeddingFailed(batch=batch, cause=exc) from exc
# 抖动可以避免多个 worker 同时再次撞上限流窗口。
delay = base_delay * (2 ** attempt) + random.uniform(0, 0.25)
time.sleep(delay)
def classify_embedding_error(exc):
# 生产实现应根据 SDK 的状态码和异常类型建立稳定映射。
if isinstance(exc, TimeoutError):
return "timeout"
status = getattr(exc, "status_code", None)
if status == 429:
return "rate_limit"
if status in {502, 503, 504}:
return "service_unavailable"
return "permanent"
class EmbeddingFailed(Exception):
# 保存 batch 与 cause,便于后续按原始序号补偿。
def __init__(self, batch, cause):
super().__init__(str(cause))
self.batch, self.cause = batch, cause

示例中的 429、502、503、504 只是常见的接入层映射示例,最终要按实际供应商的响应契约调整。尤其是 400 类输入错误,重试只会放大延迟和费用;如果服务返回明确的 Retry-After,应优先遵守它,并在本地仍保留最大尝试次数。
用索引绑定结果,处理部分成功
向量写入时不要把“第几个返回值”当成业务主键。每条输入都应带有原始序号或稳定文档 ID,批次记录至少包含:批次 ID、输入 ID、估算 token、尝试次数、模型标识和最终状态。
当一批请求返回数量不一致、连接在响应中途断开,或者 SDK 把异常包装得不够清晰时,最安全的动作是整批进入补偿队列,而不是猜测哪些向量已经成功。若供应商明确支持逐项错误返回,则可以只重试失败索引,但仍要保存原始批次快照。
| 情况 | 处理 | 原因 |
|---|---|---|
| 单条超出长度 | 截断、拆分或失败队列 | 等待不会改变输入长度 |
| 429 限流 | 遵守 Retry-After,有限退避 | 窗口恢复后可能成功 |
| 服务 503 | 指数退避并限制次数 | 服务状态可能恢复 |
| 鉴权失败 | 立即失败并报警 | 重复请求不会修复凭据 |
| 响应数量不一致 | 不写库,保留整批快照 | 避免向量与文本错配 |
常见问题
批大小是不是越大越快?
不是。批次增大通常减少请求次数,但会增加单次延迟、内存和失败重做成本。应先用 token 预算限制,再根据服务延迟和限流反馈调整条数。
可以对所有错误都自动重试吗?
不建议。只重试等待后可能改变的错误;输入过长、参数错误和鉴权失败应直接记录上下文,修正原因后再重新入队。
为什么一定要保存原始索引?
因为批处理可能部分成功、重试后顺序改变,原始索引或稳定文档 ID 才能保证向量回写到正确对象。
把长度预算、错误分类和索引化结果保存放在同一条处理链上,Embedding 批量请求才具备可调、可恢复和可追踪的工程边界。具体上限仍以实际模型和服务商文档为准。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
278 收藏
-
131 收藏
-
491 收藏
-
187 收藏
-
科技周边 · 人工智能 | 1天前 | LoRa PEFT adapter save_pretrained adapter_model.safetensors selected_adapters save_embedding_layers483 收藏
-
485 收藏
-
386 收藏
-
356 收藏
-
441 收藏
-
266 收藏
-
370 收藏
-
485 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习