mineru-api 会泄露每个解析文档的 nproc OS 线程 (每个 ThreadPoolExecutor 线程的 OpenMP 团队数); OMP_NUM_THREADS 可以修复此问题
Search before asking | 提交之前请先搜索
- I have searched the MinerU Readme and found no similar bug report.
- I have searched the MinerU Issues and found no similar bug report.
- I have searched the MinerU Discussions and found no similar bug report.
Consult the online AI assistant
- Consulted, no solution found.
Description of the bug | 错误描述
TL;DR: mineru-api leaks ~nproc OS threads per parsed document until it plateaus at roughly 32 × nproc threads (~3000 on a 96-core host). They are not Python threads: they are OpenMP (libgomp) worker teams. Every thread that calls a torch CPU op gets its own persistent OpenMP team of nproc - 1 workers, and mineru-api runs its CPU work through asyncio.to_thread() (a ThreadPoolExecutor with up to 32 workers), so each new pool thread costs another ~95 threads that are never released. Setting OMP_NUM_THREADS fixes it; the Docker image / mineru-api does not set any default.
中文摘要: mineru-api 每解析一份文档就多出约 nproc 个 OS 线程,最终封顶在约 32 × nproc(96 核机器上约 3000)。这些不是 Python 线程,而是 libgomp 的 OpenMP 工作线程组:每个调用过 torch CPU 算子的线程都会获得一组常驻的 nproc-1 个 OpenMP 工作线程,而 mineru-api 通过 asyncio.to_thread()(最多 32 个池线程)跑 CPU 部分,于是每用到一个新的池线程就固定多出约 95 个线程且不回收。设置 OMP_NUM_THREADS 即可解决;镜像和 mineru-api 目前没有任何默认值。
测量结果 (相同 1 页 2.5 KB PDF 通过 mineru-api 解析三次,后端 hybrid-engine;线程数 = ls /proc/<mineru-api pid>/task | wc -l):
默认值(无 OMP_NUM_THREADS) |
OMP_NUM_THREADS=8 + MINERU_INTRA_OP_NUM_THREADS=8 |
|
|---|---|---|
| 启动后立即的线程数(模型预加载) | 263 | 126 |
| 解析 #1 后 | 461 (+198, 包含懒加载模型初始化) | 236 (+110, 包含懒加载模型初始化) |
| 解析 #2 后 | 558 (+97) | 245 (+9) |
| 解析 #3 后 | 655 (+97) | 254 (+9) |
+97 ≈ nproc (96) + 1 每个文档在默认配置下; +9 ≈ OMP_NUM_THREADS + 1 使用上限。 |
||
| 证据表明这些是 OpenMP 线程而不是 Python 线程: |
- 在同一进程上运行的
py-spy dump仅显示了大约 12 个 Python 线程(MainThread,MPClientEngineMonitor,少数ThreadPoolExecutor-*工作线程,QueueFeederThread),而/proc/<pid>/task显示了 600 多个条目。 libgomp.so.1和libtorch_cpu.so被映射到进程中。 libgomp 保留每个进入并行区域的主线程对应的一个工作线程组,该主线程的生命周期内;ThreadPoolExecutor工作线程由asyncio.to_thread()使用,它们永远存在,因此它们的组不会被撤销。- 增长停止在上限应用时,每个文档的增量缩小到上限值。
影响: 在长时间运行的
mineru-api中,我们必须添加一个外部监视器,该监视器在线程数超过 1500 时重新启动容器(三周内执行了 317 次预防性重启)。线程数达到这种高水平也意味着 CPU 严重超载(每个 OpenMP 线程组的大小都与整个机器相匹配),这会降低多核主机的吞吐量。 建议的修复: …
内容来源: opendatalab/MinerU