执行摘要
- 一句话:AllPool.forward 提速51%
- 推荐动作:该 PR 值得精读,其优化思路(减少 GPU-CPU 同步)具有普适性,可作为类似场景的参考。建议关注
num_scheduled_tokens_cpu 的新增属性,确保后续扩展时保持兼容。
功能与动机
该 PR 是 Issue #35631 "Pooling Model Performance Optimizations" 跟踪列表中的一项,旨在通过优化 AllPool.forward 的实现来提升 pooling 模型的性能。PR 提供的 benchmark 显示优化后 median latency 从 59.89 us 降至 39.65 us,提升约 51%。
实现拆解
- 将逐个 request 的独立切片改为一次大切片 + split:原始代码对每个 request 分别执行
hidden_states[first : last + 1],每个切片都需调用 .tolist() 将 GPU tensor 同步到 CPU,导致多次设备同步。新代码先计算整个 subgroup 的范围(group_start 到 group_end),只调用两次 .item() 和一次 .tolist(),然后通过 split(split_sizes) 一次完成所有 request 的子张量划分。
- 处理空情况:当
split_sizes 为空时,直接返回空列表,保持正确性。
- 依赖数据契约变更:优化依赖
PoolingCursor 新增的 num_scheduled_tokens_cpu 属性(由 DispatchPooler 提供),属于 data-contract 层面的改动。
关键文件:
vllm/model_executor/layers/pooler/tokwise/methods.py(模块 池化层;类别 source;类型 data-contract;符号 AllPool.forward): 核心优化文件,修改 AllPool.forward 方法,将逐个 request 切片改为一次大切片后 split,消除多次 GPU 同步。
关键符号:AllPool.forward
关键源码片段
vllm/model_executor/layers/pooler/tokwise/methods.py
核心优化文件,修改 AllPool.forward 方法,将逐个 request 切片改为一次大切片后 split,消除多次 GPU 同步。
def forward(
self,
hidden_states: torch.Tensor,
pooling_metadata: PoolingMetadata,
) -> list[TokenPoolingMethodOutputItem]:
pooling_cursor = pooling_metadata.get_pooling_cursor()
# 通过 num_scheduled_tokens_cpu 一次性获取每个 request 的 token 数(CPU 列表)
split_sizes = pooling_cursor.num_scheduled_tokens_cpu.tolist() # 只调一次 tolist
if split_sizes:
# DispatchPooler 传入的是完整 hidden_states tensor
# 先计算整个 subgroup 的起始和结束位置(只调两次 .item())
group_start = int(pooling_cursor.first_token_indices_gpu[0].item())
group_end = int(pooling_cursor.last_token_indices_gpu[-1].item()) + 1
# 一次切片得到整个 subgroup
hidden_states_group = hidden_states[group_start:group_end]
# 按 per-request token 数量 split,避免逐个 request 切片
hidden_states_lst = list(hidden_states_group.split(split_sizes))
else:
hidden_states_lst = []
if not self.enable_chunked_prefill:
return hidden_states_lst
# chunked_prefill 分支保持不变
pooling_states = pooling_metadata.pooling_states
for p, hs_chunk in zip(pooling_states, hidden_states_lst):
p.hidden_states_cache.append(hs_chunk)
output_list = list[TokenPoolingMethodOutputItem]()
for p, finished in zip(pooling_states, pooling_cursor.is_finished()):
if finished:
hidden_states_cache = p.hidden_states_cache
if len(hidden_states_cache) == 1:
output_list.append(hidden_states_cache[0])
else:
output_list.append(torch.concat(hidden_states_cache, dim=0))
p.clean()
else:
output_list.append(None)
return output_list
评论区精华
Review 评论较少,主要由 CI 自动检查(pre-commit 失败)和一些自动化 bot 评论组成。无实质性设计争议。
风险与影响
- 风险:
- 兼容性风险:优化依赖
PoolingCursor 新增的 num_scheduled_tokens_cpu 属性,如果其他调用方未设置该属性,可能导致 AttributeError。但当前仅 DispatchPooler 使用 AllPool,风险有限。
- 性能退化风险:当
split_sizes 为空时新代码正确返回空列表,无明显退化。
- 逻辑正确性:新逻辑假设
first_token_indices_gpu 和 last_token_indices_gpu 是连续的 subgroup,若后续引入非连续调用,可能出错。但当前 DispatchPooler 保证连续性。
- 影响:
- 用户影响:使用 pooling 模型的用户将获得更低的延迟,特别是 batch size 较大时收益更明显。
- 系统影响:减少 GPU 同步次数,减轻 CPU-GPU 通信压力,可能提升整体吞吐。
- 团队影响:该 PR 是 pooling 性能优化系列的一部分,完成后有助于关闭 Issue #35631。
- 风险标记:数据契约变更, 缺少新增测试覆盖
关联脉络
- PR #35127 [Perf] Pool optimizations 1: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #35330 [Perf] Pool optimizations 2: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #36159 [Perf] Pool optimizations 3: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #36710 [Perf] Pool optimizations 4: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #38139 [Perf] Pool optimizations 5: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #37347 [Perf] Pool optimizations 6: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #38559 [Perf] Pool optimizations 7: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
- PR #39113 [Perf] Pool optimizations 8: 同为 Issue #35631 跟踪的 pooling 性能优化 PR 系列之一。
参与讨论