MinerU on 海光 Z100 DCU
本档案记录环境验证、服务池实现与性能测试过程,包括各方案的验证结果、复现命令、测量数据及其来源作业号,并说明相关假设的检验依据。
主要结论
| 项 | 结果 | 依据 |
|---|---|---|
| 服务池吞吐 | 955 docs/hour(4 卡,并发 12) | soak.log |
| 稳定性 | 72 请求 0 失败 0 拒绝 | §10 |
| 故障自愈 | 压测中 kill worker,自动重建,请求不丢 | §10.3 |
| 内存泄漏 | 无(72 请求前后 RSS 零增长) | §10.4 |
| 后端选择 | pipeline 3.9 s/页;VLM 14.93 s/页不建议 | §8.1 |
| FP16 / FP32 / BF16 | 16.15 / 9.01 / 5.83 TFLOPS | §3 |
Triton / torch.compile | ✕ 架构边界 gfx906 不支持 | §4 |
| vLLM HIP kernel | ✓ 可用 PagedAttention 实测可用 | §5 |
| CPU 队列 | 产出等价,稳态慢 8.9× | §8.3 |
| 环境漂移后复现 | ✓ 通过 14 页 pipeline,返回码 0 | §14 |
Z100 跑 MinerU 生产服务完全可行,走 pipeline 后端;Triton 生态用不了,但 MinerU 不需要它。
项目背景与验证范围
前序环境状态
前序工作已完成环境搭建并产出 V2 报告,声称「全部通过」。核查后发现一个关键缺口:
ssh -p <SSH_PORT> <CLUSTER_HOST> 'ls ~/.cache/modelscope/hub ~/models ~/output 2>/dev/null'
# 全部为空
MinerU 从未解析过任何文档。 V2 报告的「端到端测试」实际测的是 magika 文件类型检测——那是 MinerU 的第一步预处理,不是解析。模型一个都没下载。
可复用的软件与数据
| 项 | 路径 / 版本 |
|---|---|
| venv | <USER_HOME>/mineru-venv-py310(Python 3.10.14) |
| torch | 2.7.1+das.opt1.dtk2604 |
| MinerU | 3.4.4(editable,源码已改 magika 兼容) |
| glibc 2.28 | <USER_HOME>/glibc-2.28-rpm/(当时路径;复验时已迁移,见 §14) |
| 包装器 | dcu-python / mineru / mineru-api |
集群资源与运行约束
cat ~/<SKILL_CONFIG>
| 约束 | 值 |
|---|---|
| 内存上限 | cpus-per-task × 3569 MB |
| DCU 队列 | kshdnormal,QOS 强制 --gres=dcu:1 |
| CPU 队列 | kshcnormal,若干节点,无 GRES 限制 |
| 计算节点外网 | ❌ 无 |
/tmp | 不跨节点共享 |
硬件能力与软件环境
首次探测结果及局限
作业 <JOB_ID>:
sbatch ~/probe_caps.slurm # 见 cluster/probe_caps.py
torch 2.7.1 devices 4
arch: gfx906:sramecc-:xnack- mem GB: 17.2
[OK] bf16 matmul: supported=True maxerr=0.250
[OK] fp16 matmul: 4.85 TFLOPS ← 可疑:比 FP32 还慢
[OK] fp32 matmul: 8.91 TFLOPS
[FAIL] triton kernel: FileNotFoundError: '.../gcc-11.2.0-install/bin/gcc'
[OK] SDPA: flash_avail=True mem_eff=True
[FAIL] torch.compile: FileNotFoundError: (同上)
[OK] torch._int_mm: ok (512, 512)
[OK] onnxruntime: 1.16.3 providers=['AzureExecutionProvider','CPUExecutionProvider']
FP16 比 FP32 慢是反常的——追查发现没做 warmup。
修正后的探测方法
作业 <JOB_ID>,加 5 次预热 + 30 次计时:
def bench(dt, n=2048, iters=30):
a = torch.randn(n, n, device="cuda", dtype=dt)
b = torch.randn(n, n, device="cuda", dtype=dt)
for _ in range(5): c = a @ b # ← 关键:预热
torch.cuda.synchronize(); s = time.time()
for _ in range(iters): c = a @ b
torch.cuda.synchronize()
return 2 * n**3 / ((time.time() - s) / iters) / 1e12
fp32: 9.01 TFLOPS
fp16: 16.15 TFLOPS ← 是 FP32 的 1.8 倍
bf16: 5.73 TFLOPS
--- 强制 rocblas/hipblas 后端 ---
fp16: 16.12 TFLOPS ← 无变化,说明本来就走 rocBLAS
bf16: 5.83 TFLOPS
3.3 最终能力表
| 能力 | 结果 | 说明 |
|---|---|---|
| FP16 GEMM | 16.15 TFLOPS | 推理应走 FP16 |
| FP32 GEMM | 9.01 TFLOPS | |
| BF16 GEMM | 5.83 TFLOPS | 软件模拟,最慢 |
torch._int_mm | ✓ | INT8 可用 |
| SDPA | ⚠ 退化 | 回落 math 后端,无 flash / mem-efficient |
| 多卡 | ✓ 4 卡 | 每卡 16 GB(报告 17.2 GB) |
| hipBLASLt | ✕ 无 gfx906 kernel | 库中只有 gfx928,运行时刷 warning |
| FP8 / MFMA | ✕ | Vega20 架构限制 |
任何含一次性开销(kernel 自动调优、JIT、模型加载)的基准都必须预热,否则可能得出方向完全相反的结论。
Triton 构建问题分析
该案例展示了多层环境配置问题的定位过程:初始错误信息并未直接指向根因。
GCC 路径配置错误
FileNotFoundError: '<USER_HOME>/software/gcc-11.2.0-install/bin/gcc'
用户 PATH 里有这个目录,但它不存在:
echo $PATH | tr ':' '\n' | grep gcc
# <USER_HOME>/software/gcc-11.2.0-install/bin ← 坏路径
ls <USER_HOME>/software/gcc-11.2.0-install/bin/gcc
# ls: cannot access ...: No such file or directory
初始错误表现为 Triton 不可用;检查发现,实际原因是 GCC 路径指向不存在的目录。
系统 GCC 版本与路径问题
指向真实存在的 gcc-11.2.0 后:
cc1: error while loading shared libraries: libisl.so.15: cannot open shared object file
逐个实测集群上的编译器:
echo "int main(){return 0;}" > /tmp/t.c
for cc in <SOFTWARE_ROOT>/compiler/gcc-11.2.0/bin/gcc \
<SOFTWARE_ROOT>/compiler/gcc-12.2.0/bin/gcc \
<SOFTWARE_ROOT>/compiler/gcc-13.3.0/bin/gcc \
/opt/rh/devtoolset-7/root/usr/bin/gcc \
<SOFTWARE_ROOT>/compiler/rocm/dtk-26.04/llvm/bin/clang; do
$cc /tmp/t.c -o /tmp/t.out 2>/dev/null && echo "OK $cc" || echo "FAIL $cc"
done
| 编译器 | 结果 |
|---|---|
| gcc-11.2.0 | ✕ 缺 libisl.so.15 |
| gcc-12.2.0 | ✓ |
| gcc-13.3.0 | ✕ 缺 libisl.so.15 |
| devtoolset-7 | ✓ |
| DTK clang | ✓ |
根本原因
用 gcc-12.2.0 后(作业 <JOB_ID>):
loc("probe_triton.py":4:0): error: unsupported target: 'gfx906'
RuntimeError: PassManager::run failed
海光 Triton 版本对照验证
海光发布了 DCU 专版 Triton,值得单独验证:
curl -sL "https://download.sourcefind.cn:65024/directlink/4/triton/DAS1.8/"
# triton-3.1.0+das.opt1.dtk2604.torch271-cp310-cp310-manylinux_2_28_x86_64.whl
装到独立目录避免污染工作 venv:
cp triton-3.1.0+...manylinux_2_28_x86_64.whl triton-3.1.0+...manylinux2014_x86_64.whl
pip install --no-deps --target=<USER_HOME>/dcu-triton-test
首次运行缺 libgcvm.so.17git,定位到 dtk-26.04/dcc/gcvm/lib(不在包装器库路径里),
补进 dcu-python 的 ALL_LIBS 后(作业 <JOB_ID>):
triton: 3.1.0 from <USER_HOME>/dcu-triton-test/triton/__init__.py
arch: gfx906:sramecc-:xnack-
[FAIL] RuntimeError: PassManager::run failed
error: unsupported target: 'gfx906' ← 与 PyPI 版完全相同
读源码确认架构白名单:
grep -ohE "gfx9[0-9]{2}[a-z]*" triton/backends/amd/compiler.py | sort -u
# gfx928 gfx936 gfx940 gfx941 gfx942
grep -ohE "gfx9[0-9]{2}[a-z]*" triton/backends/hcu/compiler.py | sort -u
# gfx928 gfx936 gfx938 gfx940 gfx941 gfx942
gfx906 不在任何列表中。
strings libtriton.so | grep gfx906 能搜到 gfx906。那是 LLVM 上游的 AMDGPU target 列表,
LLVM 认识 ≠ Triton 支持。必须实测,不能靠 strings 推断。
Triton 3.x(PyPI 版与海光 DCU 版)均不支持 gfx906,torch.compile 连带不可用。
这是架构边界,配置改不了。
vLLM 移植可行性
后端能力边界
先厘清概念(这两个名字容易混):
- VLM = Vision-Language Model,一类模型(MinerU2.5-Pro-2605-1.2B 就是)
- vLLM = 一个推理引擎软件(PagedAttention)
MinerU 的选项分两层,差一个字母:
gfx906 内核架构验证结果
海光也发布了 DCU 版 vLLM:
curl -sL "https://download.sourcefind.cn:65024/directlink/4/vllm/DAS1.8/"
# vllm-0.11.0+das.opt1.dtk2604.torch271-cp310-cp310-manylinux_2_28_x86_64.whl
解包检查编译目标:
strings vllm/_C.abi3.so | grep -oE "gfx[0-9]{3,4}" | sort -u
# gfx906 gfx926 gfx928 gfx936 gfx938
strings vllm/_moe_C.abi3.so | grep -oE "gfx[0-9]{3,4}" | sort -u
# gfx906 gfx926 gfx928 gfx936 gfx938
海光在 Triton 里排除了 gfx906,却在 vLLM 的 HIP kernel 里保留了它。
5.3 实测(作业 <JOB_ID>)
from vllm import _custom_ops as ops
# PagedAttention —— vLLM 的核心
ks = torch.tensor(1.0, device="cuda"); vs = torch.tensor(1.0, device="cuda")
ops.paged_attention_v1(out, q, kc, vc, nkvh, 1.0/(hd**0.5),
btab, slen, bs, bs*2, None, "auto", ks, vs, 0,0,0,64,0)
arch: gfx906:sramecc-:xnack-
[OK] import vllm 0.11.0
[OK] vllm._C loaded
[OK] rms_norm (HIP): maxerr=0.0010
[OK] rotary_embedding (HIP): ran
[OK] paged_attention_v1 (HIP) ★: out(4, 8, 64) finite=True
结论与未完成事项
Z100 上 vLLM 有真实通路——核心 HIP kernel 全部可用,与 Triton 的结论方向相反。
未解决:平台检测失败(is_rocm: False)。vLLM 靠 import amdsmi 判断 ROCm:
# vllm/platforms/__init__.py:109
import amdsmi
amdsmi.amdsmi_init()
而 DTK 26.04 只有旧的 rocm_smi(.hyhal/rocm_smi/bin/rsmiBindings.py)。
需要 amdsmi shim 或 patch rocm_platform_plugin() 才能走通完整推理。
对 MinerU 无收益:vLLM 的价值在 PagedAttention + 连续批处理,针对高并发长文本 生成;MinerU 的 VLM 负载是单页图 + 短输出,用不上。若要在 Z100 上做 LLM 推理服务, 这条路值得继续。
解析流程问题与修复结果
每次报错都不同,说明在逐层前进。按实际遇到的顺序:
6.1 模型缺失
modelscope 会 import torch,在登录节点必炸(无 DCU 驱动)。改用 huggingface_hub
(纯 Python)+ hf-mirror:
# 探测可达性
curl -s -o /dev/null -w "%{http_code}" https://hf-mirror.com/ # 200
curl -s -o /dev/null -w "%{http_code}" https://huggingface.co/ # 被墙
# cluster/dl_models.py
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
os.environ["HF_HOME"] = "<USER_HOME>/mineru-models/hf"
from huggingface_hub import snapshot_download
for repo in ["opendatalab/MinerU2.5-Pro-2605-1.2B",
"opendatalab/PDF-Extract-Kit-1.0"]:
snapshot_download(repo, max_workers=8)
下载 20 GB。配置本地路径:
{
"models-dir": {
"pipeline": "<USER_HOME>/mineru-models/models/OpenDataLab--PDF-Extract-Kit-1.0/snapshots/master",
"vlm": "<USER_HOME>/mineru-models/models/OpenDataLab--MinerU2.5-Pro-2605-1.2B/snapshots/master"
},
"model-source": "local",
"device-mode": "cuda"
}
6.2 libssl.so.3 not found
File "mineru/cli/fast_api.py", line 19, in
import uvicorn
ImportError: libssl.so.3: cannot open shared object file
Error: Local mineru-api exited before becoming healthy.
根因:MinerU CLI 会 fork 一个 mineru-api 子进程,用裸 sys.executable 启动,
绕过 dcu-python 包装器。
当时的绕法:改走 do_parse() Python API,不经 CLI。
后来的根治(服务化必须):把 /usr/local/lib64 加进包装器 ALL_LIBS。
6.3 operator torchvision::nms does not exist
torchvision 0.21.0 是 PyPI 通用版,与 DCU torch 2.7.1 ABI 不匹配。
DCU 版 torchvision 在 sourcefind 的 vision/ 目录(不是 torchvision/):
curl -sL "https://download.sourcefind.cn:65024/directlink/4/vision/DAS1.8/"
# torchvision-0.22.0+das.opt1.dtk2604.torch271-cp310-...whl ← 与 torch271 配套
pip 拒绝 manylinux_2_28(系统 glibc 2.17)。重打标签强装——运行时本来就在 glibc 2.28 下,pip 的平台检查是多余的:
W=torchvision-0.22.0+das.opt1.dtk2604.torch271-cp310-cp310-manylinux_2_28_x86_64.whl
cp $W ${W/manylinux_2_28/manylinux2014}
pip install --no-deps --force-reinstall ${W/manylinux_2_28/manylinux2014}
6.4 缺 OCR / 版面依赖
pip install shapely pyclipper omegaconf einops ftfy
6.5 BrokenProcessPool
concurrent.futures.process.BrokenProcessPool:
A process in the process pool was terminated abruptly
根因:MinerU 渲染 PDF 用 spawn 进程池:
# mineru/utils/pdf_image_tools.py:160
if start_method != "spawn":
return ProcessPoolExecutor(max_workers=max_workers,
mp_context=multiprocessing.get_context("spawn"))
spawn 重新 exec sys.executable,丢掉全部库路径。
解法——sitecustomize.py,Python 启动时自动 import,全局生效:
# /lib/python3.10/site-packages/sitecustomize.py
import os
_w = "<USER_HOME>/mineru-venv-py310/bin/dcu-python"
if os.path.exists(_w):
import multiprocessing
multiprocessing.set_executable(_w)
try:
multiprocessing.get_context("spawn").set_executable(_w)
except Exception:
pass
验证:
dcu-python -c "
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor
def f(x): return x*x
if __name__=='__main__':
with ProcessPoolExecutor(2, mp_context=mp.get_context('spawn')) as ex:
print(list(ex.map(f,[1,2,3])))"
# [1, 4, 9]
调用 MinerU 的脚本必须有 if __name__ == "__main__": 保护。
spawn 子进程会 import 主模块;没有保护则整个脚本被重复执行(日志里 [info]
打印两次),随后 BrokenProcessPool。MinerU 文档未说明这点。
6.6 Unsupported model IR version: 10
onnxruntime.capi.onnxruntime_pybind11_state.Fail:
Load model from .../PP-LCNet_x1_0_table_cls.onnx failed:
Unsupported model IR version: 10, max supported IR version: 9
onnxruntime 1.16.3 上限 IR v9,MinerU 的表格分类模型是 IR v10。
V2 报告判定「onnxruntime ≥1.17 仅发布 manylinux_2_28,不兼容」——但用 6.3 的 重打标签手法可解,而且这不是可选优化,是必需项:
curl -sL "https://pypi.tuna.tsinghua.edu.cn/simple/onnxruntime/" \
| grep -oE 'href="[^"]*onnxruntime-1\.20\.1-cp310-cp310-manylinux_2_27_x86_64[^"]*"'
curl -sL -o ort.whl "https://pypi.tuna.tsinghua.edu.cn/packages/63/47/.../onnxruntime-1.20.1-...whl"
cp ort.whl onnxruntime-1.20.1-cp310-cp310-manylinux2014_x86_64.whl
pip install --no-deps --force-reinstall onnxruntime-1.20.1-cp310-cp310-manylinux2014_x86_64.whl
6.7 附带修复
| 问题 | 修法 |
|---|---|
onnxruntime 刷 pthread_setaffinity 报错 | 设 OMP/ORT/OPENBLAS/MKL_NUM_THREADS=4 |
libgcvm.so.17git 缺失 | 补 dtk-26.04/dcc/gcvm/lib 进 ALL_LIBS |
No module named mineru.cli.api | 3.4.4 里模块叫 fast_api,修 mineru-api 包装器 |
解析质量验证
作业 <JOB_ID>,Mooncake 论文 23 页 0.58 MB。
dcu-python parse_api.py ~/sourcecode/Mooncake/Mooncake-v3.pdf ~/parse-out/api-pipeline pipeline
[RESULT] backend=pipeline elapsed=146.8s
[OUT] .../Mooncake-v3.md chars=81072
processing-window multi-file infer finished, cost: 90.52, speed: 0.254 page/s
产出文件:
Mooncake-v3.md 81 KB Markdown 正文
Mooncake-v3_content_list.json 121 KB 结构化内容
Mooncake-v3_middle.json 2.1 MB 中间表示
Mooncake-v3_layout.pdf 835 KB 版面可视化
Mooncake-v3_span.pdf 832 KB span 可视化
images/ 26 文件
逐项核对(不满足于「作业返回 0」):
| 要素 | 结果 | 核对方式 |
|---|---|---|
| Markdown | 81,072 字符 | 标题层级、上标署名 ♠♡、摘要结构正确 |
| 表格 | 3 个 HTML | 抽样核对 LRUCache/LFUCache 数值行完整 |
| 图像 | 23 引用 / 26 文件 | images/ 实际落盘 |
| 标题 | 31 个 | 层级与原文一致 |
| 行内公式 | 17 处 LaTeX | $T_{queue}$、$\mathrm{MLP}$、$T_{prefill}$ |
| 显示公式 | 0 个 | 原文确实用行内数学,非漏检 |
首页输出片段:
# Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving
Ruoyu Qin♠♡1 Zheming Li♠1 ... ♠Moonshot AI ♡Tsinghua University
## Abstract
Mooncake is the serving platform for Kimi, a leading LLM service provided by Moonshot AI...
初次只 grep $$ 显示块得到「0 个公式」,据此差点判定公式识别失效。
实际这篇系统论文通篇用行内数学,middle.json 有 38 个 inline_equation,
MFR 识别 19 个区域。按内容实际形态选检验方法,不能用一种格式的缺席推断功能缺失。
性能测试与结果分析
8.1 三种后端
| 后端 | 作业 | 速度 | 结论 |
|---|---|---|---|
| pipeline (DCU) | <JOB_ID> | 3.9 s/页(90.5 s / 23 页) | 生产选用 |
| pipeline (CPU) | <JOB_ID> | 10.1 s/页(231.3 s) | 产出等价,见 §8.3 |
| VLM (transformers) | <JOB_ID> | 14.93 s/页 | 50 分钟超时未完成 |
VLM 慢 3.8 倍且 gfx906 上无加速手段(Triton 不可用 ⇒ vllm-engine 路径走不通), 生产不建议启用。
验证 VLM 确实在算而非挂死:
ssh <COMPUTE_NODE> "cat /proc/<PID>/status | grep -E '^State|^Threads'; ls /proc/<PID>/fd | grep -c kfd"
# State: R (running) Threads: 44 kfd handles: 1
8.2 模型加载主导
模型加载耗时占比:
模型加载占约 85%。 这直接否定「一文档一作业」的架构——85% 机时花在反复加载 同一批模型上。必须常驻进程批量喂。
8.3 CPU 队列等价性
产出逐字节比对:
diff ~/parse-out/cpu/Mooncake-v3/auto/Mooncake-v3.md \
~/parse-out/api-pipeline/Mooncake-v3/auto/Mooncake-v3.md
# 5 行差异:
# 98,99c98
# < ## Listing 1: Request samples.
# ---
# > Listing 1: Request samples.
81,074 vs 81,072 字符,差异仅一处标题层级。 CPU 队列是生产可用路径,非降级方案。
但单文档对比会严重低估差距:
| 单文档 | 批量稳态 | |
|---|---|---|
| DCU | 146.8 s | 19.6 s |
| CPU | 315.4 s | 173.8 s |
| 比值 | 2.1× | 8.9× |
单文档两侧都被模型加载主导(CPU/DCU 上耗时相近),稀释真实差距 4 倍。
CPU 路径代价集中在公式识别:MFR 在 DCU 上 5.86 it/s,CPU 上 3.85 s/it(约 22×)。
8.4 批量吞吐(无 HTTP)
作业 <JOB_ID>(DCU)、<JOB_ID>(CPU):
dcu-python throughput.py ~/pdf-corpus ~/parse-out/tput pipeline 4
| 配置 | 冷启动计入 | 稳态 | 稳态单文档 |
|---|---|---|---|
| 4 DCU | 233 docs/h | 735 docs/h | 19.6 s |
| 4 CPU worker | 63 docs/h | 83 docs/h | 173.8 s |
4 卡负载均衡:各 worker 总耗时 176.4 / 177.4 / 185.3 / 185.7 s,偏差 < 6%。
发现的 bug:批量跑完 worker 不退出,4 进程各占 3.8 GB RSS 挂死。
MinerU 的 _get_pdf_render_executor() 创建常驻 ProcessPoolExecutor,
非 daemon 子进程阻止父进程退出。修法:
from mineru.utils.pdf_image_tools import shutdown_pdf_render_executor
shutdown_pdf_render_executor() # worker 收尾时调用
修复后 CPU 批量作业正常打印 BENCH_DONE 并 RC=0 退出。
队列配置比较
实测时的集群负载:
sinfo -p kshdnormal -o "%t %D" -h | sort # alloc 735 / idle 2 (满载)
sinfo -p kshcnormal -o "%t %D" -h | sort # idle 1106 (空闲)
总吞吐 = 单节点速度 × 能拿到的节点数:
- 少量空闲 DCU 节点 × 735 = 约 1,470 docs/h
- 若干空闲 CPU 节点 × 83 = 约 91,663 docs/h
量级差距来自可用性,不是算力。大批量离线解析应铺 CPU 队列; DCU 留给时延敏感的在线请求和公式密集语料。
服务池架构与实现
网络可达性与系统架构
作业 <JOB_ID>。先验证计算节点能否对外提供服务:
# 计算节点上起服务,写出 endpoint
IP=$(hostname -I | awk '{print $1}'); PORT=$((30000 + RANDOM % 5000))
dcu-python svc_hello.py $PORT &
echo "$IP:$PORT" > ~/svc_endpoint.txt
# 登录节点访问
curl --max-time 8 "http://$(cat ~/svc_endpoint.txt)/"
# MINERU_SVC_ALIVE on <COMPUTE_NODE> ← 通
| 测试 | 结果 |
|---|---|
计算节点绑 0.0.0.0 自测 | ✓ |
| 登录节点 → 计算节点端口 | ✓ |
| 计算节点 → 外网 | ✕(预期) |
集群内网互通,与「计算节点无外网」是两件事。服务池因此能真正对外服务。
9.2 架构
设计依据(每条都来自实测,不是先验):
| 决策 | 依据 |
|---|---|
| 每卡一进程 | MinerU 模型非线程安全,单进程内并发会串行化 |
HIP_VISIBLE_DEVICES 钉卡 | 显存隔离,一个 worker OOM 不影响其他 |
| 每 worker 并发上限 = 1 | 压测得出,见 §10.1 |
| 文件 mtime 做心跳 | worker 自注册自摘除,网关只扫目录,无需额外服务发现 |
| 失败重试到别的 worker | 解析是幂等的,可安全重试 |
9.3 MinerU API 端点
grep -n "@app\.\(get\|post\)" -A3 mineru/cli/fast_api.py
| 路由 | 用途 |
|---|---|
POST /file_parse | 同步解析 |
POST /tasks | 异步提交 |
GET /tasks/{id} /tasks/{id}/result | 异步查询 |
GET /health | 健康检查(网关用) |
9.4 关键实现片段
网关调度(scripts/mineru_gateway.py):
def _acquire(exclude=()):
"""Reserve a slot on the least-loaded healthy worker, waiting if all busy."""
deadline = time.time() + SLOT_WAIT_TIMEOUT
with _cv:
while True:
cands = [(v["inflight"], v["served"], u)
for u, v in _workers.items()
if v["healthy"] and u not in exclude
and v["inflight"] < MAX_INFLIGHT] # ← 限流
if cands:
cands.sort()
url = cands[0][2]
_workers[url]["inflight"] += 1
return url
if time.time() >= deadline: return None
_cv.wait(2.0) # ← 排队而非失败
失败重试:
for attempt in range(MAX_ATTEMPTS):
url = _acquire(exclude=tuple(tried)) # 排除已失败的 worker
...
except urllib.error.HTTPError as e:
if e.code < 500: # 4xx 是客户端问题,不重试
return
_release(url, False)
tried.append(url)
worker 监督(cluster/mineru_worker.sh):
while true; do
mineru-api --host 0.0.0.0 --port $PORT &
APID=$!
# 等健康后再注册,避免流量打到还在加载模型的 worker
for i in $(seq 1 90); do
curl -sf --max-time 3 "http://127.0.0.1:$PORT/health" >/dev/null && READY=1 && break
sleep 2
done
[ "$READY" = 1 ] && echo "{\"url\":\"$URL\",...}" > "$REGFILE"
# 心跳:健康就 touch,网关靠 mtime 判活
while kill -0 $APID <PID>>/dev/null; do
curl -sf --max-time 5 ".../health" >/dev/null && touch "$REGFILE"
sleep 10
done
rm -f "$REGFILE"; sleep 5 # 崩了就重启
done
9.5 启动
sbatch ~/mineru_pool.slurm # cluster/mineru_pool.slurm
[t+95s] healthy workers: 4
=== POOL READY ===
{"pool_size": 4, "healthy": 4, "max_inflight_per_worker": 1, ...}
从登录节点验证:
curl -s "http://<SERVICE_ENDPOINT>/pool/status" | python3 -m json.tool
服务池性能与稳定性测试
网关版本性能比较
同样 24 请求 / 并发 8:
python3 loadtest.py <SERVICE_ENDPOINT> ~/pdf-corpus 8 2
| 指标 | v1(无限流无重试) | v2(生产版) |
|---|---|---|
| 错误率 | 8.3%(2 × 502) | 0.0% |
| 吞吐 | 374 docs/h | 440 docs/h(+18%) |
| 负载分布 | 11 / 4 / 5 / 2 | 6 / 5 / 7 / 6 |
| p50 | 27.2 s | 38.0 s |
| p90 | 111.9 s | 121.7 s |
原始输出:data/loadtest.log、data/loadtest2.log
限流反而提升吞吐——MinerU 模型非线程安全、请求串行处理,往忙碌 worker 堆请求 只会内部排队。偏斜从 5.5:1 收敛到 1.4:1。
p50 上升是预期且正确的:v1 的低 p50 是少数请求抢到空闲 worker 的假象, 代价是 p90 拖长和 8.3% 失败。v2 让所有请求公平排队。
超额并发测试(3× worker 数)
48 请求 / 并发 12:
python3 loadtest.py <SERVICE_ENDPOINT> ~/pdf-corpus 12 4
total=48 ok=48 failed=0 error_rate=0.0%
wall=180.9s throughput=955 docs/hour concurrency=12
latency p50=32.4s p90=70.4s p99=131.1s min=11.9s max=131.1s
per-worker distribution: 11 / 11 / 14 / 12
压测中途的池状态:
"in_flight": 4 "waiting_for_slot": 8 "queue_peak": 8 "retries": 0 "rejected": 0
背压正确:在途硬顶 4,超出的 8 个排队而非失败。过载降速,不丢请求。
并发 8→12 吞吐 440→955 docs/h,说明并发 8 时管道未打满。 955 已超过纯批处理的 735——批处理按卡静态分片有空转,服务池按空闲槽位动态分发。
压测期间故障恢复测试
PID=$(python3 -c "import json;print(json.load(open('.../<COMPUTE_NODE>-dcu3.json'))['pid'])")
ssh <COMPUTE_NODE> "kill -9 $PID" # PID <PID>
50 秒后:
before: "healthy": 3 "total_served": 5 "total_failed": 0
after: "healthy": 4 "total_served": 16 "total_failed": 0
验证确实重建(PID 变化 + 日志):
cat ~/mineru-registry/<COMPUTE_NODE>-dcu3.json
# {"url":"http://<SERVICE_ENDPOINT>",...,"pid":<PID>} ← 原 <PID>
grep -a dcu3 pool-*.err | grep -E "restart|READY|exited"
# [worker dcu3] exited, restarting in 5s (total restarts: 1)
# [worker dcu3] starting on http://<SERVICE_ENDPOINT> (restart #1)
# [worker dcu3] READY -> registered http://<SERVICE_ENDPOINT>
在真实负载下验证,total_failed 全程 0,请求未丢失。
内存稳定性测试(72 次请求)
ssh <COMPUTE_NODE> "ps -u <USER> -o rss,cmd --sort=-rss | grep -a fast_api | awk '{print \$1/1024}'"
| w0 | w1 | w2 | w3 | |
|---|---|---|---|---|
| 前 | 4839 | 4780 | 4754 | 4744 |
| 后 | 4837 | 4773 | 4762 | 4742 |
无增长。进程数稳定 4,无 spawn 子进程堆积。
10.5 汇总
未采用方案与适用限制
记录失败路径与其代价,避免重复投入。
11.1 patchelf 烧路径进 ELF ✕ 失败
动机:ld-linux 包装器只覆盖顶层进程,想用 patchelf 把库路径写进 Python
二进制,让所有子进程自动继承。理论上比包装器干净。
patchelf --set-interpreter $GLIBC_DIR/ld-linux-x86-64.so.2 python3.10
Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37:
_dl_call_libc_early_init: Assertion `sym != NULL' failed!
加 rpath 后仍 segfault,连 ldd 都崩:
patchelf --set-interpreter ... --force-rpath --set-rpath "$G:/usr/local/lib64:/usr/lib64" python3.10
./python3.10 -c "print(1)" # Segmentation fault
ldd python3.10 # Segmentation fault
分项定位:
| 测试 | 结果 |
|---|---|
| 只改 rpath,保留系统 interpreter | ✓ 正常 |
| 只换 interpreter | ✕ 断言失败 |
| 两者都改 | ✕ segfault |
glibc 2.28 的 ld.so 与系统 libc 2.17 混用不可行。已从备份回滚。
改 ELF 这条路在 glibc 大版本跨越时走不通,只能用包装器 + sitecustomize.py。
11.2 VLM 后端 ⚠ 能跑但不实用
14.93 s/页,50 分钟超时未完成 23 页。gfx906 上无加速手段:
Triton 不可用 ⇒ vllm-engine / lmdeploy 引擎路径都走不通,只能 transformers
单序列推理。pipeline 后端已能出高质量结果且快 3.8 倍。
11.3 vLLM 完整推理 ⏸ 未完成,非失败
核心 HIP kernel 实测可用(§5),但平台检测卡在 import amdsmi。
DTK 26.04 只有旧的 rocm_smi。需要写 shim 或 patch。这条路是通的,只是没走完。
11.4 V2 报告中已记录的失败路径
| 路径 | 结果 | 原因 |
|---|---|---|
| Conda 装 onnxruntime | ✕ | channels 访问超时 |
| Singularity 容器 | ✕ | Docker Hub 被墙 |
| Python 3.8 | ✕ | MinerU 需 ≥3.10 |
| Python 3.12 + onnx 1.17 强装 | ✕(当时) | 本次用重打标签手法解决了同类问题 |
初始假设检验
工作过程中三次自我更正,都影响了结论方向。记录在此因为方法论比结论本身更有价值。
无预热测出 FP16 4.85 TFLOPS(比 FP32 的 8.91 还慢),会得出「gfx906 上 FP16 没意义」的结论。预热后 16.15 TFLOPS,是 FP32 的 1.8 倍。
任何含一次性开销的基准都必须预热。
初步判断曾将 vLLM 在 gfx906 上的可用性与 Triton 支持状态关联。该判断未区分组件依赖层次。
vLLM 的 PagedAttention 是手写 HIP kernel,不经 Triton。实测海光把
_C.abi3.so 编译进了 gfx906,paged_attention_v1 正常工作。
不要用一个组件的失败推断另一个组件,即使它们看起来相关。分层验证。
单文档 315.4 s vs 146.8 s = 2.1×,据此写了「CPU 队列只是稍慢」的建议。 批量稳态实测 173.8 s vs 19.6 s = 8.9×,差 4 倍。
原因:单文档两侧都被模型加载主导,而模型加载在 CPU/DCU 上耗时相近,稀释了真实差距。
含固定启动开销的对比必须用稳态数据。
初次只 grep $$ 显示块,得到「0 个公式」。实际这篇论文通篇用行内数学,
有 17 处 $...$,middle.json 里 38 个 inline_equation。
按内容实际形态选检验方法,不能用一种格式的缺席推断功能缺失。
2026-09-27 环境复验时,顶层 Python 已经通过用户态 glibc 2.28 启动,但
MinerU CLI 拉起的本地 API 子进程重新使用系统 glibc 2.17,报
GLIBC_2.27 not found。根因是运行时闭包未传递到该子进程边界,并非 wheel 本身失效。
加载 GCC 12.2 与 DTK 26.04 module、用同一个 loader 启动主进程,并让 multiprocessing 子进程继承该入口后,14 页 pipeline 完整解析并返回 0。
动态库兼容性必须沿完整进程树验证;父进程 import 成功,不能证明 CLI、服务进程和 spawn worker 使用了同一运行时。
部署与运行配置
交付形态是裸金属,不含 Slurm。
运行环境兼容性与发行版要求
集群上那套 ld-linux 包装器是为绕开 CentOS 7 的 glibc 2.17 而生的,
代价是子进程盲区(§6.2、§6.5)。裸金属你控制 OS,这个坑本可以不踩。
DCU torch 实际要求 GLIBC_2.25:
objdump -T torch.libs/libevent_core*.so* | grep -oE "GLIBC_[0-9.]+" | sort -u -V | tail -3
# GLIBC_2.14
# GLIBC_2.17
# GLIBC_2.25 ← 真实要求
用 sort -V,不能用 sort -u——后者按字典序会把 2.9 排在 2.25 之后,
得出「只需 GLIBC_2.9」的假结论。
另外:两个 torch wheel(manylinux_2_17 / _2_28)md5 完全相同
(7b098c2f9c036fcdb9ae244228bac822),只是标签不同,别指望换标签那个能省事。
选 Rocky 8+ / Ubuntu 20.04+ / CentOS 8+,sitecustomize.py、包装器、
重打标签这些手法全部不需要。
13.2 部署
./mineru-baremetal-deploy.sh preflight # 检查 glibc / DTK / venv / 模型
sudo ./mineru-baremetal-deploy.sh install # 生成 systemd unit
sudo ./mineru-baremetal-deploy.sh start
./mineru-baremetal-deploy.sh status
systemd 负责开机自启与崩溃重启(Restart=always)。
TimeoutStartSec=300 给模型加载留时间;注册脚本等健康检查通过后才注册,
避免流量打到还在加载模型的 worker。
13.3 调用
curl -X POST http://:8000/file_parse \
-F "files=@paper.pdf" -F "backend=pipeline"
curl http://:8000/pool/status
响应头 X-MinerU-Worker 标明处理的 worker,X-MinerU-Attempt 标明第几次尝试。
13.4 监控接入
| 字段 | 告警建议 |
|---|---|
healthy | < 期望值 报警 |
waiting_for_slot | 持续 > 2× worker 数 → 需扩容 |
total_failed | 增速异常报警 |
retries | 突增说明有 worker 不稳定 |
rejected | > 0 立即排查 |
13.5 扩容
横向线性扩展,不需改网关:新机器部署 worker(不启网关), 注册表目录指向共享存储(NFS/GPFS)上同一路径,网关自动发现。
单机上限由卡数决定。若 CPU 成瓶颈(版面分析、OCR 前处理是 CPU 密集的),
降低 *_NUM_THREADS 或减少 worker 数。
环境恢复与复现验证
2026-09-27,在原实验目录经过归档和路径迁移后,从零审计运行时闭包,并重新完成一份 14 页 PDF 的端到端 pipeline 解析。
昆山 module 系统没有独立 glibc module。可复现条件是: GCC 12.2 + DTK 26.04 module、用户态 glibc 2.28 loader、正确的本地模型配置, 以及主进程和 multiprocessing 子进程共用同一个 Python 启动入口。
14.1 复现范围与证据边界
| 层次 | 验证内容 | 证据类型 |
|---|---|---|
| 登录节点 | module 清单中无独立 glibc;GCC 12.2、DTK 26.04 可用 | module avail/show |
| 计算节点 | Torch 导入、设备枚举、FP32/FP16/BF16 GEMM | 1 卡受控探针 |
| 进程树 | 主 Python、PDF render spawn worker 使用同一 loader | multiprocessing 实测 |
| 端到端 | 14 页 PDF → Markdown / JSON / 图片 / 可视化 PDF | pipeline 返回码 0 |
公开版省略用户名、集群地址、节点名、实际作业号和真实目录,只保留版本、错误类型、 命令结构、性能数字与验收条件。
环境状态审计
不要直接重跑旧命令。先检查包装器、配置和模型路径是否仍然存在:
module -t avail 2>&1 | grep -Ei '(^|/)glibc([/-]|$)|compat-glibc'
# 预期:无输出。glibc 2.28 不是 module 提供的。
for f in \
<VENV>/bin/dcu-python \
<VENV>/bin/mineru \
<VENV>/bin/mineru-api \
<VENV>/lib/python3.10/site-packages/sitecustomize.py
do
echo "=== $f ==="
sed -n '1,80p' "$f"
done
test -x <GLIBC_ROOT>/lib64/ld-linux-x86-64.so.2
test -x <VENV>/bin/python3.10
test -f <MODEL_CONFIG>
test -d <PIPELINE_MODEL_ROOT>
本次复验发现,旧的四个入口仍硬编码已经被归档的 glibc 目录; 模型权重仍在,但配置文件移动到了新的脚本目录。环境内容未必丢失,入口路径可能先失效。
Module 环境配置
module purge
module load compiler/gcc/12.2.0 compiler/dtk/26.04
source <VENV>/bin/activate
module list
# compiler/gcc/12.2.0
# compiler/dtk/26.04
gcc --version | head -1
# gcc (GCC) 12.2.0
echo "$ROCM_PATH"
# <SOFTWARE_ROOT>/compiler/rocm/dtk-26.04
DTK module 提供 HIP/数学库;GCC module 提供新版 libstdc++.so.6。
两者都不替代 glibc。只加载 DTK、或只把 glibc 放进 --library-path,
仍可能报 CXXABI_1.3.8 not found。
统一 Python 启动入口
不要分别维护多个稍有差异的包装器。建立一个入口,CLI、服务和 spawn worker 都调用它:
#!/bin/bash
set -u
GLIBC_ROOT="${GLIBC_ROOT:-<USER_HOME>/softwares/runtime/glibc-2.28}"
GCC_ROOT="${GCC_ROOT:-<SOFTWARE_ROOT>/compiler/gcc-12.2.0}"
DTK_ROOT="${DTK_ROOT:-<SOFTWARE_ROOT>/compiler/rocm/dtk-26.04}"
VENV="${VENV:-<USER_HOME>/mineru-venv-py310}"
LD_SO="$GLIBC_ROOT/lib64/ld-linux-x86-64.so.2"
PYTHON="$VENV/bin/python3.10"
ALL_LIBS="$GLIBC_ROOT/lib64:$GCC_ROOT/lib64:\
$DTK_ROOT/.hyhal/rocm_smi/lib:$DTK_ROOT/lib:$DTK_ROOT/lib64:\
$DTK_ROOT/hip/lib:$DTK_ROOT/dcc/lib:$DTK_ROOT/dcc/gcvm/lib:\
/usr/local/lib64:/usr/lib64"
exec "$LD_SO" --library-path "$ALL_LIBS" "$PYTHON" "$@"
保存为 <RUN_DIR>/dcu-python 并执行 chmod +x。预检:
<RUN_DIR>/dcu-python - <<'PY'
import ctypes, platform, sys
import onnxruntime, torch
print("python", sys.version.split()[0])
print("libc", platform.libc_ver())
print("torch", torch.__version__)
print("onnxruntime", onnxruntime.__version__)
print("devices", torch.cuda.device_count())
print("arch", torch.cuda.get_device_properties(0).gcnArchName)
PY
本地模型配置
不要依赖旧工作目录里的默认文件名。为复现实验显式指定一个配置:
{
"models-dir": {
"pipeline": "<PIPELINE_MODEL_ROOT>",
"vlm": "<VLM_MODEL_ROOT>"
},
"model-source": "local",
"device-mode": "cuda"
}
export MINERU_MODEL_SOURCE=local
export MINERU_DEVICE_MODE=cuda
export MINERU_TOOLS_CONFIG_JSON=<MODEL_CONFIG>
export HF_HUB_OFFLINE=1
export TRANSFORMERS_OFFLINE=1
若配置路径不存在,模型初始化会在较晚阶段报
AttributeError: 'NoneType' object has no attribute 'get'。
这不是模型损坏,也不是 DCU 算子错误。
spawn 子进程的动态库加载配置
MinerU 3.4.4 的 PDF render executor 使用 spawn。复现脚本必须在创建进程池前覆盖 executable:
import multiprocessing
LAUNCHER = "<RUN_DIR>/dcu-python"
multiprocessing.set_executable(LAUNCHER)
try:
multiprocessing.get_context("spawn").set_executable(LAUNCHER)
except Exception:
pass
生产环境可以把相同逻辑放进 sitecustomize.py;一次性复现实验更建议在脚本中显式设置,
避免修改共享 venv。两种方式都要求 launcher 路径真实存在。
14.7 直接调用 pipeline API
本次复验不走 mineru.cli.client:旧 CLI 会启动本地 API 子进程,
若该子进程使用裸 sys.executable,就会重新落回系统 glibc 2.17。
import multiprocessing
import sys, time
from pathlib import Path
LAUNCHER = "<RUN_DIR>/dcu-python"
multiprocessing.set_executable(LAUNCHER)
try:
multiprocessing.get_context("spawn").set_executable(LAUNCHER)
except Exception:
pass
from mineru.cli.common import do_parse, read_fn
def main():
pdf = Path(sys.argv[1])
output = Path(sys.argv[2])
data = read_fn(str(pdf))
started = time.perf_counter()
do_parse(
output_dir=str(output),
pdf_file_names=[pdf.stem],
pdf_bytes_list=[data],
p_lang_list=["ch"],
backend="pipeline",
)
print(f"PARSE_ELAPSED={time.perf_counter() - started:.3f}")
if __name__ == "__main__":
main()
spawn 会重新 import 主模块;没有
if __name__ == "__main__":,子进程会重复执行整份解析脚本。
14.8 最小 Slurm 作业
#!/bin/bash -l
#SBATCH -p <DCU_PARTITION>
#SBATCH --gres=dcu:1
#SBATCH --cpus-per-task=8
#SBATCH --mem=27gb
#SBATCH --time=00:20:00
#SBATCH -J mineru-reproduce
#SBATCH -o <LOG_DIR>/mineru-reproduce-%j.out
#SBATCH -e <LOG_DIR>/mineru-reproduce-%j.err
set -u
module purge
module load compiler/gcc/12.2.0 compiler/dtk/26.04
source <VENV>/bin/activate
export HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1
export MINERU_MODEL_SOURCE=local
export MINERU_DEVICE_MODE=cuda
export MINERU_TOOLS_CONFIG_JSON=<MODEL_CONFIG>
export OMP_NUM_THREADS=8
export ORT_NUM_THREADS=8
<RUN_DIR>/dcu-python \
<RUN_DIR>/reproduce.py \
<INPUT_PDF> \
<OUTPUT_DIR>
rc=$?
echo "PARSE_EXIT_RC=$rc"
exit "$rc"
提交后同时检查调度器状态和工作负载退出码:
sbatch reproduce.slurm
sacct -j <JOB_ID> -X -o State,ExitCode,Elapsed -P -n
grep -E "PARSE_ELAPSED|PARSE_EXIT_RC" <LOG_FILE>
# COMPLETED | 0:0
# PARSE_EXIT_RC=0
结果与产物验证
| 项目 | 2026-09-27 复验 |
|---|---|
| 输入 | 14 页技术论文 PDF |
| pipeline 返回码 | 0 |
| 冷启动解析时间 | 142.9 s |
| 模型初始化 | 17.2 s |
| processing-window 推理 | 45.6 s,0.307 page/s |
| FP32 / FP16 / BF16 | 9.123 / 16.147 / 5.764 TFLOPS |
| Markdown | 30,483 bytes |
middle.json | 893,576 bytes |
| 提取图片 | 27 文件 |
test -s <OUTPUT_DIR>/*/auto/*.md
test -s <OUTPUT_DIR>/*/auto/*_middle.json
test -s <OUTPUT_DIR>/*/auto/*_layout.pdf
find <OUTPUT_DIR> -type f | sort
grep -c '^#' <OUTPUT_DIR>/*/auto/*.md
grep -o '!\[\](images/' <OUTPUT_DIR>/*/auto/*.md | wc -l
返回码 0 只是第一层验收;还应检查 Markdown 非空、结构化 JSON 能解析、 图片实际落盘,以及标题、公式和图注是否出现在内容中。
错误信息与定位方法
| 现象 | 真实原因 | 处理 |
|---|---|---|
ld-linux... No such file / rc 127 | 包装器硬编码旧 glibc 目录 | 重新解析现存 runtime 路径,不改系统 ELF |
CXXABI_1.3.8 not found | 只加载 glibc/DTK,缺 GCC runtime | 加载 GCC 12.2 module,并把其 lib64 放入闭包 |
GLIBC_2.27 not found | CLI 启动的 API 子进程绕过 loader | 直调 do_parse(),或修好服务入口包装器 |
BrokenProcessPool | spawn worker 使用裸 Python | multiprocessing.set_executable() |
NoneType ... get | 模型配置路径已移动 | 显式设置并预检 MINERU_TOOLS_CONFIG_JSON |
pthread_setaffinity 刷屏 | ONNX Runtime 线程亲和性与 cgroup 不匹配 | 限制线程;本次不影响结果正确性 |
torchvision.io 的 zlib warning | 可选 image extension 解析到系统 zlib | 本次 pipeline 成功;若使用该 extension 必须另做功能验证 |
这套步骤证明 MinerU 3.4.4 pipeline 可在该 Z100 / DTK 26.04 环境中恢复运行, 不代表所有 CLI 服务入口、VLM 引擎或未来版本自动继承兼容性。升级任何 wheel、 module 或模型后,都应从 14.2 的审计重新开始。
附录:命令参考
15.1 环境
# 连接
ssh -p <SSH_PORT> <CLUSTER_HOST>
# 作业里必须先
module purge
module load compiler/gcc/12.2.0 compiler/dtk/26.04
source <USER_HOME>/mineru-venv-py310/bin/activate
# 离线声明(计算节点无外网)
export HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1
export MINERU_MODEL_SOURCE=local
export MINERU_DEVICE_MODE=cuda
export MINERU_TOOLS_CONFIG_JSON=<MODEL_CONFIG>
# 唯一可用的 gcc(gcc-11.2.0 / 13.3.0 都缺 libisl.so.15)
export CC=<SOFTWARE_ROOT>/compiler/gcc-12.2.0/bin/gcc
# 多 worker 共享机器时限制线程
export OMP_NUM_THREADS=4 ORT_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4
# CentOS 7 复现必须通过 §14 的统一 launcher 启动 Python;
# 裸 python / 裸 sys.executable 会回到系统 glibc 2.17。
15.2 作业模板
#!/bin/bash
#SBATCH -p kshdnormal
#SBATCH --gres=dcu:4 # QOS 强制至少 1
#SBATCH --cpus-per-task=32
#SBATCH --mem=111gb # 上限 = cpus × 3569 MB
#SBATCH --time=02:00:00
#SBATCH -o job-%j.out
#SBATCH -e job-%j.err
module purge
module load compiler/gcc/12.2.0 compiler/dtk/26.04
source <USER_HOME>/mineru-venv-py310/bin/activate
...
rc=$?; echo "EXIT_RC=$rc"; exit $rc # ★ 必须,否则 sacct 状态会骗人
先验证配额不真排队:
sbatch --test-only job.slurm
# "Job N to start at ..." → 配额合法
# "Requested node configuration is not available" → 无空闲节点(配额没问题)
# "too much memory" → 超配额
15.3 服务池
~/mineru_pool_ctl.sh status # 作业 + 存活 worker + 网关健康
~/mineru_pool_ctl.sh scale 2 # 保持 2 个池作业(每个 4 卡)
~/mineru_pool_ctl.sh stop # 停止并清注册表
~/mineru_pool_ctl.sh endpoints # 打印网关地址
15.4 压测
python3 loadtest.py
python3 loadtest.py <SERVICE_ENDPOINT> ~/pdf-corpus 12 4
15.5 排查
# ★ 不要只看 sacct 的 State,必须读日志
sacct -j <ID> -X -o State,Elapsed,ExitCode -P -n
# onnxruntime 会刷屏,过滤掉
grep -av "pthread_setaffinity" job-<ID>.err | tail -30
# 确认进程在算还是挂死
ssh "cat /proc//status | grep -E '^State|^Threads'"
ssh "ls /proc//fd | grep -c kfd" # >0 说明持有 DCU 句柄
# 查 wheel 的真实 glibc 要求(必须 sort -V)
objdump -T .so | grep -oE "GLIBC_[0-9.]+" | sort -u -V | tail -3
15.6 manylinux 重打标签强装
运行时已在 glibc 2.28 下时,pip 的平台检查是多余的:
cp pkg-1.0-cp310-cp310-manylinux_2_28_x86_64.whl \
pkg-1.0-cp310-cp310-manylinux2014_x86_64.whl
pip install --no-deps --force-reinstall pkg-1.0-cp310-cp310-manylinux2014_x86_64.whl
15.7 海光 DCU 软件下载
# 目录结构(注意 torchvision 在 vision/ 下)
curl -sL "https://download.sourcefind.cn:65024/directlink/4/"
curl -sL "https://download.sourcefind.cn:65024/directlink/4/vision/DAS1.8/"
curl -sL "https://download.sourcefind.cn:65024/directlink/4/vllm/DAS1.8/"
curl -sL "https://download.sourcefind.cn:65024/directlink/4/triton/DAS1.8/"
版本必须与 torch 严格配套,如 ...das.opt1.dtk2604.torch271。
数据与作业记录索引
| 数据 | 作业号 | 原始文件 |
|---|---|---|
| 硬件能力(初测) | <JOB_ID> | data/job-logs.txt |
| 硬件能力(含预热) | <JOB_ID> | 同上 |
| 首次成功解析 | <JOB_ID> | data/sample-parse-output.md |
| CPU 单文档 | <JOB_ID> | data/job-logs.txt |
| VLM 后端 | <JOB_ID> | 同上 |
| DCU 批量吞吐 | <JOB_ID> | 同上 |
| CPU 批量吞吐 | <JOB_ID> | 同上 |
| 海光 Triton | <JOB_ID> | 同上 |
| vLLM kernel | <JOB_ID> | 同上 |
| 服务池 v1 | <JOB_ID> | data/loadtest.log |
| 服务池 v2 | <JOB_ID> | data/loadtest2.log、data/soak.log |
| 环境漂移恢复 | <JOB_ID> | 脱敏后的 module、loader、配置路径与错误链记录 |
| 2026-09-27 完整复验 | <JOB_ID> | 14 页 pipeline 日志与 Markdown / JSON / 图片产物清单 |