opensource_community
完整实验档案 · 脱敏公开版

MinerU on 海光 Z100 DCU

本档案记录环境验证、服务池实现与性能测试过程,包括各方案的验证结果、复现命令、测量数据及其来源作业号,并说明相关假设的检验依据。

集群 测试集群(国产超算) 硬件 海光 Z100 DCU (gfx906) × 4 系统 CentOS 7.6 / glibc 2.17 软件栈 DTK 26.04 时间 2026-08-12 ~ 2026-08-13;复验 2026-09-27 交付形态 裸金属(systemd)
01

主要结论

项结果依据
服务池吞吐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 / BF1616.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 不需要它。

02

项目背景与验证范围

前序环境状态

前序工作已完成环境搭建并产出 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)
torch2.7.1+das.opt1.dtk2604
MinerU3.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不跨节点共享
03

硬件能力与软件环境

首次探测结果及局限

作业 <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 GEMM16.15 TFLOPS推理应走 FP16
FP32 GEMM9.01 TFLOPS
BF16 GEMM5.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、模型加载)的基准都必须预热,否则可能得出方向完全相反的结论。

04

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 连带不可用。 这是架构边界,配置改不了。

05

vLLM 移植可行性

后端能力边界

先厘清概念(这两个名字容易混):

  • VLM = Vision-Language Model,一类模型(MinerU2.5-Pro-2605-1.2B 就是)
  • vLLM = 一个推理引擎软件(PagedAttention)

MinerU 的选项分两层,差一个字母:

后端(用什么方法解析) ├── pipeline 专用小模型:版面 + OCR + 公式 + 表格 ├── vlm-engine 用 VLM 大模型端到端读整页 └── hybrid-engine └─ vlm-engine 内部再选引擎 ├── transformers 纯 PyTorch,最慢 ├── vllm-engine ← vLLM 在这里 └── lmdeploy

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 推理服务, 这条路值得继续。

06

解析流程问题与修复结果

每次报错都不同,说明在逐层前进。按实际遇到的顺序:

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.api3.4.4 里模块叫 fast_api,修 mineru-api 包装器
07

解析质量验证

作业 <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」):

要素结果核对方式
Markdown81,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 个区域。按内容实际形态选检验方法,不能用一种格式的缺席推断功能缺失。

08

性能测试与结果分析

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 模型加载主导

模型加载耗时占比:

146.8s
单文档(冷启动)
22.5s
批量中的同一文档
~85%
耗时占比:模型加载

模型加载占约 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 队列是生产可用路径,非降级方案。

但单文档对比会严重低估差距:

单文档批量稳态
DCU146.8 s19.6 s
CPU315.4 s173.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 DCU233 docs/h735 docs/h19.6 s
4 CPU worker63 docs/h83 docs/h173.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 留给时延敏感的在线请求和公式密集语料。

09

服务池架构与实现

网络可达性与系统架构

作业 <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 架构

┌───────────────────────────────┐ 客户端 ──HTTP──►│ 网关 :38080 │ │ 健康探测 · 最小负载调度 │ │ 每 worker 限流 · 失败重试 │ └───────┬───────────────────────┘ │ 按空闲槽位分发 ┌───────────┬───────┴───────┬───────────┐ ▼ ▼ ▼ ▼ worker :38100 :38101 :38102 :38103 DCU 0 DCU 1 DCU 2 DCU 3 │ │ │ │ └───────────┴───────┬───────┴───────────┘ ▼ 注册表(共享目录,文件 mtime = 心跳)

设计依据(每条都来自实测,不是先验):

决策依据
每卡一进程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
10

服务池性能与稳定性测试

网关版本性能比较

同样 24 请求 / 并发 8:

python3 loadtest.py <SERVICE_ENDPOINT> ~/pdf-corpus 8 2
指标v1(无限流无重试)v2(生产版)
错误率8.3%(2 × 502)0.0%
吞吐374 docs/h440 docs/h(+18%)
负载分布11 / 4 / 5 / 26 / 5 / 7 / 6
p5027.2 s38.0 s
p90111.9 s121.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}'"
w0w1w2w3
前4839478047544744
后4837477347624742

无增长。进程数稳定 4,无 spawn 子进程堆积。

10.5 汇总

72
累计请求(24+48)
0
失败
0
拒绝
955/h
峰值吞吐
11

未采用方案与适用限制

记录失败路径与其代价,避免重复投入。

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 强装✕(当时)本次用重打标签手法解决了同类问题
12

初始假设检验

工作过程中三次自我更正,都影响了结论方向。记录在此因为方法论比结论本身更有价值。

12.1「FP16 无收益」← 未预热

无预热测出 FP16 4.85 TFLOPS(比 FP32 的 8.91 还慢),会得出「gfx906 上 FP16 没意义」的结论。预热后 16.15 TFLOPS,是 FP32 的 1.8 倍。

教训

任何含一次性开销的基准都必须预热。

12.2「vLLM 在 gfx906 上没戏」← 用 Triton 的失败推断 vLLM

初步判断曾将 vLLM 在 gfx906 上的可用性与 Triton 支持状态关联。该判断未区分组件依赖层次。 vLLM 的 PagedAttention 是手写 HIP kernel,不经 Triton。实测海光把 _C.abi3.so 编译进了 gfx906,paged_attention_v1 正常工作。

教训

不要用一个组件的失败推断另一个组件,即使它们看起来相关。分层验证。

12.3「CPU 只慢 2.1 倍」← 用单文档数据外推批量

单文档 315.4 s vs 146.8 s = 2.1×,据此写了「CPU 队列只是稍慢」的建议。 批量稳态实测 173.8 s vs 19.6 s = 8.9×,差 4 倍。

原因:单文档两侧都被模型加载主导,而模型加载在 CPU/DCU 上耗时相近,稀释了真实差距。

教训

含固定启动开销的对比必须用稳态数据。

12.4「公式识别失效」← 只查了一种格式

初次只 grep $$ 显示块,得到「0 个公式」。实际这篇论文通篇用行内数学, 有 17 处 $...$,middle.json 里 38 个 inline_equation。

教训

按内容实际形态选检验方法,不能用一种格式的缺席推断功能缺失。

12.5「ONNX Runtime 已无法运行」← 子进程绕过 loader

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 使用了同一运行时。

13

部署与运行配置

交付形态是裸金属,不含 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 数。

14

环境恢复与复现验证

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 GEMM1 卡受控探针
进程树主 Python、PDF render spawn worker 使用同一 loadermultiprocessing 实测
端到端14 页 PDF → Markdown / JSON / 图片 / 可视化 PDFpipeline 返回码 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()
main guard 的必要性

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 / BF169.123 / 16.147 / 5.764 TFLOPS
Markdown30,483 bytes
middle.json893,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 foundCLI 启动的 API 子进程绕过 loader直调 do_parse(),或修好服务入口包装器
BrokenProcessPoolspawn worker 使用裸 Pythonmultiprocessing.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

附录:命令参考

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 / 图片产物清单
初版:2026-08-13 · 环境恢复复验:2026-09-27 · 数据来自脱敏后的实测记录 · 公开报告版本