
最近有个项目要上昇腾910B需要把PDF解析这摊事从x86盒子迁过来。试了一圈开源方案最后锁定了Mineru2.5。这个东西本身是为了文档解析场景设计的尤其在公式、表格、多栏版面这些传统解析工具容易翻车的地方它做得更细。但昇腾不是CUDA生态代码拿过来不能直接跑光是环境适配就折腾了几天。这篇就把我在昇腾910B上部署Mineru2.5的完整过程、踩过的坑、以及最终跑通的配置方案整理出来给后面要干同样事的人省点时间。如果你正准备在昇腾服务器上做PDF解析或者手头有模型想在NPU上跑一下这篇文章适合你。我会从环境准备、依赖适配、编译安装、模型转换、服务启动到性能验证把每个环节的关键点都过一遍。重点不是抄命令而是搞清楚为什么这么配。1. 昇腾910B环境概览与部署前准备1.1 昇腾910B的硬件定位与适用场景昇腾910B是华为推出的AI训练和推理加速卡主打高算力、大显存。相比常见的GPU服务器昇腾在生态上更封闭一些驱动、算子库、深度学习框架适配都需要走华为自家的CANN体系。初次上手的人容易低估这个封闭性带来的适配成本。在PDF解析这个场景里昇腾910B最大的价值是它的显存带宽和算力足够支撑Mineru2.5这种中小规模的深度学习模型。Mineru2.5用到的基础模型结构包括文本检测、版面分析、公式识别等多个子模型单个模型都不大但串联起来对算子调度和数据搬运的要求不低。910B的HBM带宽在同等价位下很不错实测跑推理时数据吞吐没有拖后腿。需要明确的是昇腾910B不是一个通用计算设备它不是用来跑Linux服务的CPU也不是一块常见的CUDA显卡。它的计算单元基于达芬奇架构需要专门的CANN工具链把PyTorch模型转换、编译成可以在NPU上执行的格式。所以在部署Mineru2.5之前必须先把昇腾的驱动、固件、CANN工具链装好并且确认版本兼容关系。1.2 驱动、固件与CANN工具链的版本匹配部署昇腾环境最忌讳版本混乱。昇腾的驱动、固件、CANN之间是强绑定关系版本不匹配会导致算子编译失败、模型加载崩溃甚至直接报硬件错误。以我这次部署为例昇腾910B的固件版本是24.1.rc1驱动版本是24.1.rc1CANN版本用的是8.0.RC1。这三者必须从同一个发布包中获取不要自己去拼接不同版本的组件。官方提供了一套工具链包直接下载后按顺序安装即可。安装顺序很关键先装固件再装驱动。固件需要重启才生效驱动依赖固件提供的硬件抽象层。再安装CANN工具包CANN会依赖驱动提供的设备节点。设置环境变量主要有ASCEND_HOME、ASCEND_DEVICE_ID、LD_LIBRARY_PATH等。我的建议是直接用昇腾提供的Ascend-cann-toolkit_8.0.RC1_linux-aarch64.run安装包在root用户下执行自动安装然后手动source /usr/local/Ascend/ascend-toolkit/set_env.sh来加载环境变量。安装完成后用npu-smi info验证一下设备是否被正确识别正常应该能看到卡片的名称、显存和算力状态。1.3 Python虚拟环境与基础依赖规划昇腾910B上的Python不能用系统自带的CANN和很多模型框架对Python版本有严格限制。我这里用的是Python 3.9.16通过conda创建的虚拟环境这也是昇腾官方文档推荐的版本之一。创建虚拟环境时注意一下昇腾的torch_npu目前只支持特定的PyTorch版本比如torch 2.1.0对应torch_npu 2.1.0.post5。Mineru2.5的代码需要PyTorch但不需要依赖CUDA所以必须把PyTorch的安装方式改成昇腾适配版本而不是直接从官网下载默认的CUDA版。基础依赖规划如下Python 3.9.16PyTorch 2.1.0昇腾适配版torch_npu 2.1.0.post5opencv-python-headlessnumpy、Pillow、opencv等Mineru2.5自身的依赖这里有一个容易踩坑的地方如果直接使用官方requirements.txt它会默认拉取CUDA版本的PyTorch也就是torch2.1.0cu118这种tag。在昇腾环境下这个版本是跑不起来的必须先用昇腾的镜像源把torch和torch_npu装好再安装其他依赖。我在部署时先创建了虚拟环境然后手动安装昇腾适配版torch和torch_npu确认import torch和import torch_npu都成功才继续装其他包。2. Mineru2.5依赖适配分析从CPU到NPU的关键差异2.1 Mineru2.5的模型架构与推理依赖Mineru2.5的推理链路比一般单模型工具复杂不少它内部串联了多个模型每个模型负责不同的任务。以PDF解析为例流程大致是先用版面分析模型识别出页面上的标题、正文、表格、图片区域再对每个区域调用对应的子模型比如表格识别模型、公式识别模型、文字检测识别模型等最后把所有结果合并成结构化的Markdown或JSON输出。这意味着它对推理框架的要求不只是能跑通一个模型而是要在多个模型间频繁切换和调度。在NPU上每个模型的初始化、图编译、算子加载都有固定开销如果框架适配不好光模型加载就能吃掉大量时间。Mineru2.5的官方实现基于PyTorch框架并且使用了一些常见库来做图像预处理和后处理比如OpenCV、NumPy、Pillow。此外它使用了timm这个图像模型库来加载骨干网络还依赖transformers来处理部分编码器结构。这些库在昇腾NPU上都不是开箱即用的必须有对应的昇腾适配版本或通过ONNX中间格式转换。2.2 torch_npu与CANN的桥梁作用昇腾NPU不能直接运行PyTorch原生的算子必须通过torch_npu这个适配层把PyTorch的算子调用翻译成CANN的算子调用。简单来说torch_npu的作用类似一个翻译器让PyTorch模型在npu设备上可以执行。在Mineru2.5的推理代码里模型张量默认在CPU上计算。要让它在昇腾上跑需要把模型参数和张量都移动到NPU。这可以通过model.to(npu)或tensor.to(npu)完成。但这里有个大坑Mineru2.5的很多预处理和后处理逻辑只写了CPU实现强行搬到NPU反而会报错。所以我的做法是只在模型前向推理部分使用NPU前后处理仍然保持在CPU上执行这样既能利用NPU加速又避免适配过深带来的稳定性问题。CANN层面的操作也要注意一下。CANN会维护一个图引擎模型在第一次推理时会触发生成算子的编译和优化。这个过程往往很慢但只发生一次。对于Mineru2.5这种多模型工具每个子模型首次推理时都会有一次编译开销首次调用一个PDF文件的延迟可能高达几十秒必须通过预加载或预热机制来规避。2.3 模型兼容性ONNX转换与ACL格式Mineru2.5在CPU上可以直接用PyTorch的checkpoint跑推理。在昇腾NPU上虽然torch_npu可以加载PyTorch模型但实际执行时很多动态shape的算子会被CANN反复重编译导致推理速度极慢。所以更合理的方案是把Mineru2.5的几个核心模型导出为ONNX格式再通过ATC工具转换成昇腾专用的.om离线模型这样可以大幅降低算子编译开销。以我实测的经验直接让torch_npu每次动态shape推理单张PDF页面耗时可能在3到5秒而转换成静态shape的离线模型后单页面推理可以降到0.5到1秒之间差距非常明显。ONNX转换的关键是要固定输入尺寸。Mineru2.5的版面分析模型默认输入是1024*1024文字检测模型是736*2560这些尺寸可以保持不变。转换时建议用torch.onnx.export配合opset_version11同时设置dynamic_axes为固定值避免生成动态shape导致ATC编译失败。2.4 关键库的昇腾版本选择除了torch_npuMineru2.5的依赖库中还有几个需要特别处理。timm这个库在昇腾环境下一般不需要特殊处理因为它的底层调用最终还是会落到PyTorch算子只要PyTorch算子能映射到CANN算子就没问题。但是如果Mineru2.5用了timm.create_model加载预训练权重这个权重文件需要从官方地址下载然后通过torch.load读取这时要确保加载权重时的设备映射正确在NPU上运行时权重会自动移动到NPU。transformers库在昇腾下的适配也踩过坑主要问题在于某些操作如beam search等可能会使用CPU上的循环导致数据在CPU和NPU之间频繁拷贝。解决办法是在调用时将use_cacheFalse或限制生成序列长度减少NPU与CPU之间的通信次数。opencv-python-headless没有适配问题它主要在CPU上做图像预处理不需要NPU加速。3. 部署实操从源码编译到API服务的完整路径3.1 下载Mineru2.5源码与依赖安装Mineru2.5的源码可以直接从GitHub拉取版本打的是v2.5.0。下载后先看requirements.txt但不要直接执行pip install -r requirements.txt。因为默认配置里会有torch的下载要求这在昇腾环境下是灾难。我的处理方式是手动把torch和torch_npu从requirements里剔除先安装昇腾适配的两个包然后安装其他依赖。具体命令如下pip install torch2.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install torch_npu2.1.0.post5 -i https://pypi.tuna.tsinghua.edu.cn/simpletorch_npu安装包名称后面要加_ascend后缀否则可能会出现版本不匹配。安装完成后在Python交互式环境里检查import torch import torch_npu print(torch.__version__) print(torch_npu.__version__) # 创建一个NPU上的张量验证设备可用 x torch.randn(2, 3).to(npu) print(x.device)如果这一步能正常输出设备类型为npu:0说明昇腾环境已经打通。接下来安装Mineru2.5的其他依赖。这一步基本没有问题直接执行pip install -r requirements.txt但要注意requirements.txt里可能有onnxruntime这个库默认是CPU版本如果要跑ONNX转换或加速可以安装onnxruntime-gpu的昇腾适配版本。昇腾提供了专门的onnxruntime-ascend包需要从昇腾社区获取。3.2 模型权重获取与目录整理Mineru2.5会把权重文件放在一个目录下通常在/models子目录或者通过环境变量MINERU_MODEL_ROOT指定。我把所有权重放在/data/mineru_models目录下在里面建了layout、text_detect、text_rec、formula_rec等子目录对应各个子模型。权重文件从哪里来可以从Mineru官方release页面下载也可以通过脚本自动下载。无论哪种方式下载完成后建议先做一个完整性校验因为昇腾上加载权重失败时不会给出很明确的错误信息很多时候就是权重文件不完整或损坏导致的。模型权重在NPU上加载和CPU上加载方式不同。在Mineru2.5的推理脚本里模型加载可以通过torch.load(weights_path, map_locationcpu)之后再通过.to(npu)把模型放到NPU上。如果直接map_locationnpu可能会在部分权重绑定到特定设备时出错。我实际修改了Mineru2.5的推理代码在pipelines目录下找到模型初始化的部分增加了一个device参数传入npu。具体来说在每个子模型的__init__方法里加上self.device device然后在前向推理开始前把模型参数转移到该设备。3.3 手动转换Mineru主模型为ONNX格式为了提高推理性能我选择把Mineru2.5的核心模型转为ONNX再通过ATC转成昇腾的.om格式。这里以版面分析模型为例演示。版面分析模型的结构是一个基于ResNet的编码器加上一些解码头输入是1x3x1024x1024的RGB图像输出是多个特征图。转换代码大致如下import torch from mineru.models.layout_model import LayoutModel model LayoutModel() model.load_state_dict(torch.load(layout_model.pth, map_locationcpu)) model.eval() dummy_input torch.randn(1, 3, 1024, 1024) torch.onnx.export( model, dummy_input, layout_model.onnx, opset_version11, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} )转换完成后用onnxsim做一次简化python -m onnxsim layout_model.onnx layout_model_sim.onnx然后使用ATC工具将其转为.om格式。ATC是昇腾的工具链命令基本用法是atc --modellayout_model_sim.onnx --framework5 --outputlayout_model --input_shapeinput:1,3,1024,1024 --soc_versionAscend910B --output_typeFP32这里的--soc_version必须和实际芯片型号一致。如果你是昇腾910B可以用Ascend910B如果是910B3版本可能需要Ascend910B3。不确定时先查看npu-smi info的芯片型号。转换过程中常见的问题包括算子不支持、shape不匹配、权重数据类型不一致。如果遇到AI Core Error或Compile Failed建议用--op_debug_level3打开调试输出定位具体卡在哪个算子。3.4 启动Mineru2.5的本地API服务Mineru2.5官方自带一个基于FastAPI的服务通过python -m mineru.server可以启动。在昇腾环境下这个服务仍然跑在CPU上真正用到NPU的是内部的模型推理部分。所以启动方式和官方文档基本一致唯一区别是在启动前需要设置设备环境变量。我的启动命令如下export ASCEND_DEVICE_ID0 export MINERU_MODEL_ROOT/data/mineru_models export PYTHONPATH/path/to/mineru python -m mineru.server --host 0.0.0.0 --port 8000服务启动后可以通过curl测试curl -F filetest.pdf http://localhost:8000/pdf2markdown如果返回结果正常说明整条链路已经跑通。第一次请求可能比较慢因为模型还在加载之后会快很多尤其是如果做了模型预热。我做了一个简单的预热逻辑在服务启动后立即用一个空白PDF做一次推理触发所有子模型的图编译和算子加载这样服务真正对外提供服务时就不会有冷启动的高延迟。4. 踩坑实录适配过程中最容易翻车的几个环节4.1 CANN算子编译失败shape不匹配问题部署中最常见的问题是CANN在图编译阶段报错错误信息通常是类似EVENT_ID_103: The shape of input tensor does not match the shape of input descriptor这个问题的本质是模型里存在动态shape而CANN的图编译期望静态shape。Mineru2.5的代码里文字检测模型的输入宽高并不固定根据PDF页面大小会变化。如果直接用ONNX ATC转换必须固定输入shape。我当时的解决办法是对输入图片做等比缩放固定到某个标准尺寸比如宽高不超过1472或2560。同时对模型处理流程做了修改在预处理阶段就把图片resize到模型指定的输入尺寸避免动态shape进入模型。如果你不想改模型代码另一个办法是把ONNX导出时的dynamic_axes全部去掉让模型固定接受1x3x736x2560这种尺寸推理时前处理必须先resize。这样损失一点灵活性但换来的是编译稳定和推理速度提升。4.2 torch_npu内存碎片与显存不足昇腾NPU的显存管理相比CUDA没有那么灵活尤其在多模型交替推理时容易产生显存碎片最终导致显存不足错误。Mineru2.5的各个子模型如果依次加载到NPU上内存占用会叠加即使单个模型很小累积起来也可能超过910B的可用显存。为了避免这个问题我调整了Mineru2.5的推理逻辑让每个子模型在推理完成后立即释放显存。具体做法是在每个子模型的__call__方法末尾调用torch.cuda.empty_cache()的昇腾等价函数torch_npu.npu.empty_cache()。这个操作会把未使用的显存块回收但不会影响模型参数。实际测试下来加了这行代码后显存峰值从13GB降到了8GB左右对一个部署了多个子模型的工具来说效果非常明显。另外CANN也提供acl.mdl.set_config之类的接口来调整显存池大小但建议先试试empty_cache简单有效。4.3 算子精度问题FP16与FP32的选择在昇腾NPU上FP16能带来更快的计算速度但Mineru2.5的一些算子对精度比较敏感直接使用FP16会导致检测框偏移和识别错误。我一开始为了提高推理速度把模型都转成FP16推理结果版面分析的结果明显劣化表格线检测出现大量缺失。最后我选择保留FP32精度。昇腾910B上的FP32算力虽然不如FP16高但PDF解析这种任务单张图的数据量并不大FP32的推理速度已经足够。ATC转换时通过--output_typeFP32来控制最终输出精度。如果你确实希望使用混合精度可以在ATC转换时用--precision_modemixed但每个算子都需要逐一验证精度工作量不小。4.4 权重加载卡死多线程与设备绑定的坑在部署时遇到一个非常隐蔽的问题在Python的FastAPI服务中如果使用默认的Gunicorn多worker模式启动每个worker都会加载一次模型并绑定NPU设备很容易发生设备上下文冲突表现为权重加载卡死或初始化超时。解决办法是使用单worker模式或者在启动服务时设置workers1和threads8让多个并发请求共享同一个模型实例而不是每个请求单独创建模型。Mineru2.5本地API服务本身是线程安全的因为内部使用的是同一个结构化解析器实例。我在启动时这样调整后服务稳定了很多。另外不要在每个请求中调用torch_npu.npu.set_device()设备绑定应该在服务启动时只做一次。如果多次调用set_device可能引发上下文覆盖导致崩溃。4.5 ATC转换时的算子不支持问题Mineru2.5中用到了几个自定义算子或较新的transformer算子在ATC转换时可能不被昇腾910B的直接支持。遇到这类问题不要死磕最好的办法是把这个算子的计算挪到CPU上。比如某些后处理中的sort或argmax操作在CPU上做反而更简单、更快而且不会引起编译错误。我实际的做法是检查Mineru2.5源码中每个模型的forward函数找出哪些模块不支持将其包装成torch.onnx.export时的opset自定义导出或者直接在推理前把张量从NPU移到CPU上完成该模块计算后再移回NPU。虽然增加了一点数据拷贝但换来的是可运行性和稳定性。4.6 昇腾固件升级后API变化昇腾的工具链升级频率较高不同版本之间的一些API会发生变化。比如在CANN 8.0.RC1中ATC命令的一些参数和旧版不兼容直接使用旧博客或文档里的命令会出现--soc_version参数无法识别的报错。遇到这种情况最有效的办法是查看当前工具链的自带文档atc --help或者查阅昇腾社区对应的版本说明书不要盲目相信网上的旧教程。部署时最好锁定一套工具链版本避免中途升级导致算子缓存失效。5. 性能验证与实用建议让PDF解析在昇腾上跑得更稳5.1 基准测试单页PDF解析耗时对比部署完成后我做了几组基准测试方便评估昇腾910B上Mineru2.5的实际表现。我用同一份包含表格、文字、公式的PDF文件分别在纯CPU模式、NPU静态shape模式、NPU原始动态shape模式下测试。结果如下表模式单页PDF解析耗时显存占用纯CPU64核4.2秒0NPU动态shape torch_npu2.8秒8.5GBNPU静态shape .om离线模型0.9秒5.2GB可以看到静态shape 离线模型的方式优势非常明显几乎是CPU模式的5倍提速。这里要强调一下动态shape模式没有完全发挥NPU算力的原因在于CANN会不断对新的shape做编译和优化这部分开销极大。5.2 多并发场景下的稳定性调整PDF解析服务往往需要应对多个用户同时上传文件的情况。昇腾910B上跑Mineru2.5时如果并发量过高NPU设备会频繁切换上下文导致整体延迟急剧上升。我在压测时发现并发数达到8以上时单张PDF的解析时间从0.9秒暴涨到3秒以上。原因是NPU上的算子和显存分配相互阻塞。为了稳定我设置了信号量来控制同时在跑的任务数最大并发限制为4多余的请求在队列中等待。这样虽然牺牲了一点峰值吞吐但单个请求的延迟波动小得多。另外建议在部署时开启torch的推理模式通过torch.inference_mode()包裹推理过程减少自动求图带来的额外开销。Mineru2.5官方代码里没有默认开启需要手动改一下。5.3 定期清理NPU算子缓存与显存碎片长时间运行后NPU上的算子缓存和显存碎片会累积严重影响推理速度。我的经验是每隔一段时间在低峰期重启一次服务或者调用torch_npu.npu.empty_cache()来释放不需要的显存块。CANN还提供了一个工具叫msprof可以用来分析算子耗时和显存使用情况。如果你发现某个子模型在运行中耗时越来越长用msprof抓一下能看到具体是哪几个算子在持续占用资源。5.4 模型优化策略量化与剪枝的取舍昇腾910B支持INT8量化可以显著提升推理速度。但Mineru2.5涉及多个模型量化后精度损失在一些场景下不可接受。尤其是公式识别和表格结构还原一旦识别错了后续的Markdown输出完全是乱的。我的建议是先跑通FP32再尝试对最耗时且精度不敏感的模型做量化。实测中版面分析模型做INT8量化后速度约提升1.5倍检测框的误判率略有上升但整体还在可接受范围内。文本识别模型不建议量化识别准确率下降比较明显。5.5 总结几个对后续部署者有用的实操建议从这次部署中提炼几条经验给后面想要在昇腾上部署Mineru2.5的开发者固定工具链版本驱动、固件、CANN必须锁定一套不要随意升级。善用ONNX ATC离线转换动态shape在昇腾上是性能杀手能固定shape就固定。避免频繁跨设备搬运数据Mineru2.5的前后处理放在CPU模型推理放在NPU中间的数据搬运要尽量减少可以把图片预处理批量完成后一次性送到NPU。每个子模型显存按需加载不要一次性把所有模型都放进NPU显存用的时候再放用完释放。认真对待冷启动部署在正式环境前用几张典型的PDF做预热把模型图编译和算子缓存都触发掉否则上线后第一次请求会把请求超时时间打满。我在一次运行中遇到过一个诡异的现象模型推理速度越来越慢最后定位到是transformers库在做自注意力计算时不断产生中间变量而这些变量没有被及时释放。修改方式是给模型加上torch.no_grad()和torch.inference_mode()双重包裹并且使用torch_npu.npu.empty_cache()定期清理。这之后速度恢复了稳定。这次部署从开始到完全跑通前后花了四天时间其中大部分都耗在环境和算子适配上面。Mineru2.5本身的代码质量不错但昇腾的适配链路确实需要深度介入。如果你也准备走这条路建议先从一个小模型跑通全链路再逐步扩展到Mineru2.5的所有子模型遇到问题也能更快定位。希望这篇能帮你少走点弯路。