[Bug]: /v1/embeddings deadlocks when a truncated input follows a short one in the same request

当单个 /v1/embeddings 请求里出现“短输入 + 需要截断的长输入”时,vLLM 会把 [Bug]: /v1/embeddings deadlocks when a truncated input follows a short one in the same request 表现为请求

快速结论:当单个 /v1/embeddings 请求里出现“短输入 + 需要截断的长输入”时,vLLM 会把 [Bug]: /v1/embeddings deadlocks when a truncated input follows a short one in the same request 表现为请求卡死、GPU 空闲、只有 Running: N reqs, Waiting: 0 reqs,优先排查 pooling 请求在 chunked prefill 下的调度器活性问题,而不是先怀疑 CUDA 或显卡故障。

适用环境:Issue 已确认环境为 vLLM 0.28.0(vllm/vllm-openai:v0.28.0)、NVIDIA RTX 3080 Ti(12 GiB)、驱动 580.173.02、Ubuntu、kernel 7.0.0-31-generic,模型 Qwen3-Embedding-4B bf16,启动参数 --runner pooling --convert embed;同版本 ROCm gfx1151 构建未复现。压缩张量 W4A16 版本也复现过。

最快修复方案:暂无确认的一步修复方案。Issue 中明确验证过的是该问题为 V1 调度器活性缺陷;可优先尝试降低并发、让每个请求不触发 chunked prefill,或避免 pooling 请求的 prompt 长度等于 max_model_len 后被分块,但这些都属于规避思路,Issue 未给出已验证的配置修复。

注意事项:提高 --max-num-batched-tokens 在 12 GiB 显卡上不可用,24576 和 65536 均在启动时 OOM;关闭编译缓存、调整批大小、怀疑 GPU 故障都没有解决问题。该问题只在 CUDA 路径复现,ROCm 构建未复现,因此不要直接套用到所有后端。

问题场景

用户用 vLLM 启动 OpenAI 兼容服务,加载 Qwen/Qwen3-Embedding-4B,使用 --runner pooling --convert embed,并通过 /v1/embeddings 传入多个输入。当同一个请求中先给一个短输入,再给一个长到需要 truncate_prompt_tokens 截断的输入时,请求永不返回。后续请求会排在它后面,只有 abort 该请求才能恢复。

Issue 后续还发现,这不只是“短后长”的排序问题。使用长度排序批处理时,单个长文档请求、总在途 token 接近默认批预算的情况下,也会出现相同卡死特征。

报错原文

[Bug]: /v1/embeddings deadlocks when a truncated input follows a short one in the same request

Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s,
            Running: 1 reqs, Waiting: 0 reqs

后续真实负载中出现的变体:

Engine 000: Avg prompt throughput: 0.0 tokens/s, Running: 4 reqs, Waiting: 0 reqs

原因分析

Issue 中已定位到根因:这是 V1 调度器活性 bug,不是 CUDA 路径问题,也不是 pooling 批处理 offset/index 问题。调度器对 pooling 请求是否需要为采样 token 预留槽位存在不一致:num_sampled_tokens_per_step 对非 diffusion runner 仍为 1,但 pooling runner 并不会追加采样 token。运行队列分支会把 num_new_tokens 限制到 max_model_len - request.num_computed_tokens - self.num_sampled_tokens_per_step,而等待队列分支没有同样的 clamp。于是当 pooling prompt 恰好为 max_model_len,且被 chunked prefill 分块时,运行分支会在 num_computed_tokens == max_model_len - 1 时把新 token 数钳到 0,请求既不能前进也不能完成,调度循环空转。

短后长的排序只是最小复现条件之一:truncate_prompt_tokens: 2048 等于 --max-model-len 2048,Qwen3-Embedding 支持 chunked prefill 且默认开启,12 GiB 显卡下默认 max_num_batched_tokens 为 2048,多个条件叠加正好触发该边界。

环境排查

  • 确认 vLLM 版本是否为 0.28.0,是否使用官方 vllm/vllm-openai:v0.28.0 镜像。
  • 确认后端是 CUDA 还是 ROCm;Issue 中 ROCm gfx1151 构建未复现。
  • 确认显卡型号与显存:RTX 3080 Ti 12 GiB 属于会触发默认 max_num_batched_tokens=2048 的区间。
  • 确认模型是否为 Qwen3-Embedding-4B,是否使用 bf16 或压缩张量 W4A16 版本。
  • 确认启动参数包含 --runner pooling --convert embed。
  • 确认 --max-model-len 与请求中的 truncate_prompt_tokens 是否相等;Issue 复现中两者均为 2048。
  • 确认 chunked prefill 是否处于默认开启状态;pooling 模型不能对 prefill 分块,但支持 chunked prefill 的模型会默认开启。
  • 确认卡死时 nvidia-smi 是否仍响应、dmesg 是否有 Xid;Issue 中无 Xid,GPU 始终可响应。

解决步骤

  1. 先复现最小用例:启动 vllm serve Qwen/Qwen3-Embedding-4B --runner pooling --convert embed --max-model-len 2048 --port 8094。
  2. 用单个请求依次发送 [SHORT]、[LONG]、[LONG, SHORT]、[SHORT, LONG],并设置 truncate_prompt_tokens: 2048。
  3. 观察只有 [SHORT, LONG] 是否卡死,以及日志是否出现 Running: N reqs, Waiting: 0 reqs 且吞吐为 0。
  4. abort 卡死请求,确认后续请求是否立刻恢复处理。
  5. 可优先尝试规避:降低并发,避免单个 pooling 请求的 prompt 长度恰好等于 max_model_len 后被分块;或避免让请求在运行队列中进入 max_model_len - 1 的边界状态。
  6. 不要优先用提高 --max-num-batched-tokens 解决;Issue 中 24576 和 65536 在 12 GiB 显卡上启动即 OOM。
  7. 如果必须使用 CUDA 构建,关注 vLLM 上游对 V1 scheduler 中 pooling 请求 num_sampled_tokens_per_step 与运行队列 clamp 的修复;在修复可用前,按上述规避方式控制并发与请求长度。

验证方法

重新发送 [SHORT, LONG] 请求,确认请求能在超时前返回 embedding,日志不再出现吞吐为 0 且 Waiting: 0 的卡死状态。再用真实 DuRetrieval 负载或长度排序批处理测试,确认在并发和总在途 token 接近默认批预算时也不会卡住。若仍出现 Running: N reqs, Waiting: 0 reqs 且 GPU 0% 利用率,说明问题未解决。

参考来源

vllm-project/vllm #55427

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27735

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注