执行摘要
- 一句话:Pooling 基准修复:度量完整响应体并支持 bytes_only
- 推荐动作:值得精读的小型 bugfix。它清晰展示了异步 HTTP 客户端中“延迟测量点”与“响应体消费”之间常见的陷阱,
iter_any() 增量消费避免内存膨胀的做法有借鉴价值;bytes_only 无 metadata 时回退客户端估算的取舍也可作为后续协议设计的参考。适合 benchmark 工具维护者和性能团队阅读。
功能与动机
PR body 明确列出三个问题:Pooling benchmarks 在响应 headers 到达时就记录 E2E 延迟,JSON 响应在解析时被消费,但二进制 bytes 响应从未被消费,因此报告的延迟不包含响应传输时间,且连接可能在服务端仍在写时被关闭;新增的 bytes_only 格式被当作 JSON 处理,每个请求都失败(报错 Attempt to decode JSON with unexpected mimetype: application/octet-stream);用确定性延迟服务器实测,main 分支 bytes 格式报告 latency 为 0.0005 秒而实际 wall 时间为 0.25 秒以上,证明测量完全失真。
实现拆解
变更集中在 vllm/benchmarks/lib/endpoint_request_func.py 的 _run_pooling_request,并按以下步骤推进:
- 调整延迟测量点:把
output.ttft = output.latency = time.perf_counter() - st 从“响应 headers 到达后立即记录”移动到“响应 body 消费完成后记录”,使延迟真正覆盖端到端传输。
- 按 encoding_format 分策略消费 body:
bytes / bytes_only 使用 async for _ in response.content.iter_any(): pass 增量读取,避免把 embedding 大数组留在内存中;float 等其他格式使用 await response.read() 完整读入后再解析。
- token 用量来源三分支:
bytes 从响应 metadata header 读取 usage;bytes_only 因该格式刻意不携带 metadata,回退为客户端侧 prompt_len;float 从 JSON body 读取 usage。
- 调用链配套透传:
async_request_openai_embeddings、async_request_vllm_rerank、async_request_openai_embeddings_chat、async_request_infinity_embeddings、async_request_vllm_pooling 五个入口均新增 prompt_len=request_func_input.prompt_len 参数传入。
- 测试配套:本 PR 未新增测试文件,作者用既有
tests/benchmarks/test_endpoint_request_func_timing.py(18 passed)加 MRV2 E2E 冒烟验证(/v1/embeddings 与 /pooling 三种格式共 128 请求全部通过)。
关键文件:
vllm/benchmarks/lib/endpoint_request_func.py(模块 基准工具;类别 source;类型 core-logic;符号 _run_pooling_request, async_request_openai_embeddings, async_request_vllm_rerank, async_request_openai_embeddings_chat): 唯一变更文件,修复 pooling 基准的延迟度量点并支持 bytes_only 格式,是整个 PR 的核心。
关键符号:_run_pooling_request, async_request_openai_embeddings, async_request_vllm_rerank, async_request_openai_embeddings_chat, async_request_infinity_embeddings, async_request_vllm_pooling
关键源码片段
vllm/benchmarks/lib/endpoint_request_func.py
唯一变更文件,修复 pooling 基准的延迟度量点并支持 bytes_only 格式,是整个 PR 的核心。
async def _run_pooling_request(
session: aiohttp.ClientSession,
api_url: str,
payload: dict[str, Any],
headers: dict[str, Any],
pbar: tqdm | None = None,
num_input_sequences: int = 1,
prompt_len: int = 0,
) -> RequestFuncOutput:
"""发送 pooling 类请求并记录端到端延迟。
修复前延迟在响应 headers 到达时即被记录,二进制 body 从未被消费,
导致延迟被低估且连接可能在服务端仍在写响应时被关闭。
"""
output = RequestFuncOutput(num_input_sequences=num_input_sequences)
st = time.perf_counter()
output.start_time = st
try:
async with session.post(url=api_url, headers=headers, json=payload) as response:
if response.status == 200:
encoding_format = payload.get('encoding_format', 'float')
if encoding_format in ('bytes', 'bytes_only'):
# 二进制 embedding 体通常较大,增量消费但不保留数据在内存
async for _ in response.content.iter_any():
pass
else:
# JSON 体相对较小,直接完整读入再解析
await response.read()
# 延迟必须在 body 消费完成后记录,才是真正的端到端延迟
output.ttft = output.latency = time.perf_counter() - st
if encoding_format == 'bytes':
# bytes 格式的 token 用量来自响应 metadata header
metadata = json.loads(response.headers['metadata'])
usage = metadata.get('usage', {})
elif encoding_format == 'bytes_only':
# bytes_only 刻意不携带 metadata,回退到客户端侧 prompt 长度
usage = {'prompt_tokens': prompt_len}
else:
data = await response.json()
usage = data.get('usage', {})
output.success = True
output.generated_text = ''
output.prompt_len = usage.get('prompt_tokens', 0)
else:
output.success = False
output.error = response.reason or ''
except Exception as e:
output.success = False
output.error = str(e)
if pbar:
pbar.update(1)
return output
评论区精华
本 PR 没有产生技术性 review 讨论。claude[bot] 因该 PR 来自 fork 而禁用自动评审;维护者 yewentao256 直接批准,评论为“LGTM, thanks for the work!”,无未解决疑虑。PR body 中作者对 bytes_only 的 prompt token 为客户端估算这一权衡做了明确说明,属于有意的设计取舍。
风险与影响
- 风险:
- 无新增自动化测试:修复依赖既有 18 个用例与作者手工 E2E 冒烟,回归保护较弱。
bytes_only token 统计偏差:prompt_len 来自客户端侧估算,与 tokenizer 真实统计可能有出入,但作者声明不影响请求成功与延迟记录。
- benchmark 请求将等待完整 body:若服务端响应 body 迟迟不发,benchmark 请求会挂住(原实现会提前关闭连接),这是端到端延迟测量的正确语义,但改变了挂起时的失败模式。
- 影响面可控:变更只落在
vllm/benchmarks/lib/endpoint_request_func.py,不触及生产 serving 代码路径。
- 影响:影响范围为性能团队与 benchmark 使用者:pooling / embedding 类基准的 E2E 延迟、成功率与 token 统计从此反映完整响应传输,bytes_only 格式可正常跑通。对生产服务、API 用户无直接影响;对依赖 benchmark 数据做性能对比的团队而言,修复后数据可信度显著提升,但历史基线数据与修复后数据不可直接对比。
- 风险标记:无新增测试覆盖, bytes_only 延迟统计为客户端估算, benchmark 请求将等待完整 body
关联脉络
- PR #56908 [Model Runner V2] Tolerate lack of pinned memory support: 本 PR 的复现与验证均基于 MRV2 pooling server(VLLM_USE_V2_MODEL_RUNNER=1),56908 是对 MRV2 平台能力的补齐,两者同属 MRV2 演进线。
参与讨论