Cluster labels: _vocab_lock is held across the whole vocabulary build, blocking the request threads its docstring says it must not

该报错描述的是 InvokeAI 图像索引服务中的锁竞争问题: _vocab_lock 在整段词汇表构建(含耗时的 ensemble_phrase_embeddings )期间被持有,而 /cluster_labels 的请求线程也要获取同一把锁,于是请求被阻塞而不是快速返回 409。首次构建嵌入缓

快速结论:该报错描述的是 InvokeAI 图像索引服务中的锁竞争问题:_vocab_lock 在整段词汇表构建(含耗时的 ensemble_phrase_embeddings)期间被持有,而 /cluster_labels 的请求线程也要获取同一把锁,于是请求被阻塞而不是快速返回 409。首次构建嵌入缓存(或索引不可用/停止)期间出现 /cluster_labels 挂起时,优先排查此项。

适用环境:Issue 中可确认的环境为 Python 3.12(验证在 CPU/测试环境、未锁定的全新环境中进行),复现基于 current main 0ff4a63f0b0aa1ad9a162a17d0c7e62eaece2112。Issue 未给出 CUDA、显卡型号或 PyTorch 版本信息,此处不补写。

最快修复方案:Issue 中未记录已合并的正式修复步骤,暂无确认的一步修复方案。社区方案将 get_vocab_embeddings() 改为非阻塞获取 _vocab_lock(与 get_vocab_build_state() 一致),忙碌时立即返回准备中的错误(经现有标签路由为 HTTP 409),可优先尝试。

注意事项:该非阻塞读取方案来自 Issue 评论中的实现与回归测试,尚未在 Issue 中确认被主线采纳;评论者明确未做 GPU 或全仓库测试声明。该锁不提供跨进程保护(见 #9624),跨进程缓存命名问题不在本方案范围内。

问题场景

在 InvokeAI 中访问 /cluster_labels 相关功能(标签/聚类标签)时触发。每次启动后首次为某个嵌入模型构建词汇表嵌入缓存期间(在缓存可持久化之前,每次重启都会触发),请求线程需要等待整段构建完成;构建耗时可能长达数分钟,期间请求挂起,而不是按设计快速返回 409“Cluster labels are still being prepared”。由于请求运行在 asyncio.to_thread 共享执行器上,长时间阻塞还可能拖慢 /points 与图像搜索。

报错原文

Cluster labels: _vocab_lock is held across the whole vocabulary build, blocking the request threads its docstring says it must not

原因分析

在 invokeai/app/services/image_index/image_index_default.py 中,_build_vocab_embeddings 的整个函数体都包在 with self._vocab_lock: 内,包括耗时数分钟的 ensemble_phrase_embeddings(self._embed_texts, vocabulary) 调用;而 get_vocab_embeddings 也获取同一把 _vocab_lock。设计意图是把构建交给索引 worker,避免它在请求线程上执行,但锁仍然让请求线程与构建串行化,于是 docstring 所警告的线程饥饿仍会通过 Lock.acquire 发生。

评论中的复现实验显示:用 1 秒模拟构建时,get_vocab_embeddings 被阻塞 1.00 秒,之后返回新构建的缓存,而不是抛出预期的 TextSearchUnavailableError("Cluster labels are still being prepared; try again shortly")。真实首次构建时该阻塞时长为分钟级,且发生在共享 asyncio.to_thread 执行器的线程上。索引不可用或已停止且锁被持有时,也存在同样影响。

环境排查

  • 确认 InvokeAI 版本/提交:复现基于 current main 0ff4a63f0b0aa1ad9a162a17d0c7e62eaece2112。
  • 确认 Python 版本:社区验证使用 Python 3.12(CPU/测试环境)。
  • 确认是否处于首次运行或缓存未持久化状态(每次重启都会触发构建)。
  • 确认索引服务状态是否可用/运行,排除索引停止导致的不可用路径。
  • 检查 /points、图像搜索等同样走共享执行器的接口是否同时变慢。
  • Issue 未提供 CUDA、PyTorch、显卡信息,如怀疑与 GPU 相关,需另行收集证据。

解决步骤

  1. 定位 invokeai/app/services/image_index/image_index_default.py,确认 _build_vocab_embeddings 与 get_vocab_embeddings 均在 _vocab_lock 下运行。
  2. 参考 get_vocab_build_state() 已使用非阻塞获取锁的做法,将 get_vocab_embeddings() 改为非阻塞获取 _vocab_lock(可优先尝试)。
  3. 锁被占用时:活跃索引返回“准备中”错误(经现有标签路由映射为 HTTP 409);索引停止或无模型时返回不可用错误。
  4. 保持 worker 对构建串行化、失效顺序以及缓存/失败结果原子发布的所有权,避免新增构建状态与发布竞态。
  5. 确保 _vocab_failure 仍只记录一次;worker 释放后不应触发另一次编码器构建,也不应为不可用请求排队无限重建。
  6. 参考 Issue 评论中给出的实现与回归用例(提交 3076d4df2f),以其为对照做本地验证。

验证方法

使用门控事件模拟耗时构建:并发发出 /cluster_labels 请求,确认请求立即返回准备中/不可用响应而非等待构建结束;构建完成后确认返回缓存结果(或已记忆的失败)且未发生第二次编码器构建。按评论验证记录,可运行以下测试:

pytest tests/app/services/image_index -q -n 0
pytest tests/app/services/image_index/test_image_index_service.py tests/app/routers/test_image_map.py -k 'labels or requests_do_not_wait or busy_vocabulary' -q -n 0

评论中报告的结果为:前者 235 passed,后者(修复后用例及相关标签/API 选择)23 passed;五个新增用例在还原上游读取逻辑时均因预期的“请求被阻塞”断言而失败。上述结果仅限 CPU/测试环境,不含 GPU 或全仓库测试结论。

参考来源

invoke-ai/InvokeAI #9626

相关 Issue:invoke-ai/InvokeAI #9624(跨进程缓存命名,引判断锁不提供跨进程保护,超出本问题范围)

评论中参考的实现提交:3076d4df2f

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 31716

发表回复

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