DeepSeek-OCR-2 昇腾 NPU 推理适配实战:基于 vLLM-Ascend 的文档 OCR 与 Markdown 输出方案
【免费下载链接】cann-recipes-infer本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-infer
本文基于 CANN / cann-recipes-infer 仓库中 integration/vllm/deepseek-ocr-2 目录下的完整适配方案,系统讲解 DeepSeek-OCR-2 文档理解模型如何在昇腾 NPU(Atlas 800I/T A2)上通过 vLLM-Ascend 完成推理部署,涵盖一键转换脚本、NPU 运行参数、MOE 算子替换原理、三种推理入口与单卡性能测试方法。读完本文,你将掌握从拉取模型到跑通图片/PDF/批量 OCR 推理并产出性能报告的完整闭环流程。
方案概览:非侵入式 NPU 适配
DeepSeek-OCR-2 是 DeepSeek 系列中面向文档理解与 OCR 识别的多模态模型,能够将扫描版或版面复杂的图片、PDF 直接识别为结构化 Markdown 文本。本项目基于 vLLM-Ascend 为其提供了一套昇腾 NPU 推理适配方案,核心特点如下(见 README.md):
- NPU 原生 MOE 算子支持:将模型中的 CUDA 实现替换为 vllm-ascend 的
fused_experts; - 非侵入式适配方案:不修改官方模型源码主体,而是通过补丁脚本注入 NPU 支持;
- 模块化设计:转换脚本、环境脚本、MOE 补丁、基准测试脚本职责分离,易于维护和升级;
- 覆盖多种输入形态:支持单图、PDF 文档和批量评估;
- 结构化 Markdown 输出:直接产出可复用的 Markdown 格式识别结果。
整个适配任务由智子芯元 KernelCAT 智能体工具自动化完成,适配产物即为仓库中的convert_to_npu.sh一键脚本与npu_patch/补丁目录。
硬件与基础环境要求
| 项目 | 要求 |
|---|---|
| 昇腾设备 | Atlas 800I/T A2 |
| 内存 | ≥ 32GB |
| 磁盘 | ≥ 50GB(用于模型存储) |
本项目基于vllm-ascend v0.8.5rc1开发,官方提供了配套容器镜像:
docker pull quay.io/ascend/vllm-ascend:v0.8.5rc1创建容器
推荐使用docker run拉起推理容器,关键点是透传昇腾设备节点并挂载驱动与模型目录:
docker run -it -d --net=host --shm-size=512g \ --privileged \ --name ds-ocr-2 \ --device=/dev/davinci_manager \ --device=/dev/hisi_hdc \ --device=/dev/devmm_svm \ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver:ro \ -v /usr/local/sbin:/usr/local/sbin:ro \ -v /data/model_weight:/data \ quay.io/ascend/vllm-ascend:v0.8.5rc1 /bin/bash参数说明:
--net=host --shm-size=512g:使用宿主机网络并放大共享内存,多模态推理与批量并发场景下避免 IPC 内存不足;--privileged与三个--device节点:让容器内可见昇腾 NPU 设备(davinci_manager、hisi_hdc、devmm_svm);-v /usr/local/Ascend/driver:ro:以只读方式挂载 NPU 驱动;-v /data/model_weight:/data:将宿主机模型目录映射到容器内/data,后续模型路径按此挂载约定填写。
模型获取
使用 ModelScope 下载 DeepSeek-OCR-2 权重:
pip install modelscope -i https://mirrors.huaweicloud.com/repository/pypi/simple # 下载模型 modelscope download --model deepseek-ai/DeepSeek-OCR-2 --local_dir /data/models/DeepSeek-OCR-2参数说明:
--model:模型名称(deepseek-ai/DeepSeek-OCR-2);--local-dir(README 中写作--local_dir):模型存储路径,示例下载到/data/models/DeepSeek-OCR-2,该路径即后续config.py中MODEL_PATH的取值。
一键转换:convert_to_npu.sh 全流程拆解
将本项目克隆到容器内/workspace目录:
cd /workspace git clone https://gitcode.com/cann/cann-recipes-infer.git cd cann-recipes-infer/integration/vllm/deepseek-ocr-2执行转换脚本:
./convert_to_npu.sh脚本默认参数为./convert_to_npu.sh [source_dir] [target_dir],其中源目录默认为DeepSeek-OCR-2/DeepSeek-OCR2-master/DeepSeek-OCR2-vllm,目标目录默认为deepseek_ocr2_npu。结合 convert_to_npu.sh 源码,脚本自动完成以下 5 步:
- 安装 Python 依赖(convert_to_npu.sh L16-L21):
einops、addict、easydict、triton-ascend、PyMuPDF、img2pdf,走华为云 PyPI 镜像加速;若个别包安装失败仅告警不中断; - 克隆 DeepSeek-OCR-2 源码(convert_to_npu.sh L23-L27):若本地不存在
SOURCE_DIR,则自动git clone --depth=1拉取官方源码并定位到 vLLM 工程目录; - 拷贝源码与补丁(convert_to_npu.sh L29-L35):将官方 vLLM 目录完整复制为
deepseek_ocr2_npu/,并把 npu_patch/deepseek_ocr2_npu.py(NPU MOE 补丁)与 npu_patch/set_env.sh(环境初始化)放入目标目录; - 应用 NPU 适配补丁(convert_to_npu.sh L37-L74):注释
deepencoderv2/sam_vary_sdpa.py中的flash_attn导入,向config.py追加 NPU 配置段,并逐一对三个官方推理脚本注入 NPU 支持; - 输出转换结果:生成
deepseek_ocr2_npu/目录,并拷贝 npu_patch/benchmark.py 作为性能测试工具。
脚本自动追加的 NPU 配置
转换脚本向config.py追加的配置段如下:
# ==================== NPU Configuration ==================== DEVICE = 'npu' ENFORCE_EAGER = True MAX_MODEL_LEN = 8192 SWAP_SPACE = 0 TENSOR_PARALLEL_SIZE = 1 GPU_MEMORY_UTILIZATION = 0.85 DISABLE_MM_PREPROCESSOR_CACHE = True各参数含义:
DEVICE = 'npu':指定推理设备为昇腾 NPU;ENFORCE_EAGER = True:关闭图模式,强制 Eager 执行,规避 NPU 上算子图捕获兼容性问题(这也是 README「适配内容」中强调的 NPU 关键配置);MAX_MODEL_LEN = 8192:模型最大序列长度;SWAP_SPACE = 0:关闭 swap 空间,防止出现不可控的 host 内存交换;TENSOR_PARALLEL_SIZE = 1:单卡张量并行,对应 README 中「性能测试(单卡)」的前提;GPU_MEMORY_UTILIZATION = 0.85:NPU 显存利用率;DISABLE_MM_PREPROCESSOR_CACHE = True:关闭多模态预处理器缓存,避免多图/PDF 场景缓存命中异常。
此外,脚本还会对run_dpsk_ocr2_image.py、run_dpsk_ocr2_pdf.py、run_dpsk_ocr2_eval_batch.py三个官方脚本做统一改造:在import torch之后注入import os、os.environ["VLLM_USE_V1"] = "0"(强制使用 vLLM V0 引擎)与import deepseek_ocr2_npu;并将所有enforce_eager=False统一替换为True、所有gpu_memory_utilization=0.75/0.9/0.7统一收敛为0.85。对图片流式脚本还会追加swap_space=0与disable_mm_preprocessor_cache=True两个引擎参数。
环境变量初始化
转换完成后进入目标目录并初始化环境:
cd deepseek_ocr2_npu source set_env.shnpu_patch/set_env.sh 完成三类环境准备:
- CANN 工具链路径:设置
ASCEND_TOOLKIT_HOME、ASCEND_HOME_PATH、ASCEND_AICPU_PATH、ASCEND_OPP_PATH,并优先 source ATB(Ascend Transformer Boost)的set_env.sh(缺失时仅告警); - 动态库搜索路径:将 driver 的
lib64、ascend-toolkit的${ARCH}-linux/lib64与ATB_HOME_PATH/lib依次加入LD_LIBRARY_PATH,同时把 CANN Python 包加入PYTHONPATH; - NPU 运行参数:
ATB_STREAM_SYNC_EVERY_KERNEL_ENABLE=0(关闭逐 kernel 流同步以提升性能)、ATB_OPSRUNNER_SETUP_CACHE_ENABLE=1、HCCL_WHITELIST_DISABLE=1、ASCEND_GLOBAL_LOG_LEVEL=3、TOKENIZERS_PARALLELISM=false,以及两个关键 vLLM 开关:VLLM_ATTENTION_BACKEND=NPU(显式指定 NPU 注意力后端)与VLLM_USE_TRITON_FLASH_ATTN=0(关闭 Triton FlashAttention,配合官方脚本中的 SDPA 路径)。
运行推理:三种入口
修改config.py中的三个关键路径参数后即可运行:
vi config.py # - MODEL_PATH: 模型路径(如 /data/models/DeepSeek-OCR-2) # - INPUT_PATH: 输入文件路径 # - OUTPUT_PATH: 输出文件路径三种推理脚本:
# 图片流式输出 python run_dpsk_ocr2_image.py # PDF 处理 python run_dpsk_ocr2_pdf.py # 图片批量处理 python run_dpsk_ocr2_eval_batch.py注意:使用批量处理脚本时,
config.py中输入图片路径应为图片文件夹路径。
三条入口的差异在于输入形态与输出组织:单图脚本面向单张图片的流式 Markdown 识别;PDF 脚本借助PyMuPDF将 PDF 按页抽取为图片再送入模型;批量脚本遍历文件夹内图片,适合数据集评估。三者统一复用官方DeepseekOCR2Processor的图像处理管线(裁剪模式由CROP_MODE控制),并在入口处完成模型注册。
NPU 适配原理:MOE 算子与注意力机制
MOE 算子:替换为 vllm-ascend 原生实现
DeepSeek-OCR-2 采用 MoE(Mixture of Experts)架构,其官方实现依赖 CUDA 的 fused MOE kernel。补丁文件 npu_patch/deepseek_ocr2_npu.py 在导入时执行_apply_moe_patch(),实现思路是运行时替换 vllm 的fused_moe符号:
- 从
vllm_ascend.ops.fused_moe引入fused_experts与select_experts; - 定义
ascend_fused_moe(hidden_states, w1, w2, gating_output, topk, renormalize, inplace, **kwargs)包装函数; - 内部先调用
select_experts(softmax 路由打分、top-k 专家选择、可选renormalize),再调用fused_experts完成专家前向计算(deepseek_ocr2_npu.py L38-L58); - 将包装函数挂载到
vllm.model_executor.layers.fused_moe.fused_moe(deepseek_ocr2_npu.py L60),后续模型前向调用统一走 NPU 原生实现。
补丁带完整的降级保护:torch_npu、vllm_ascend、vllm任一缺失时仅打印 WARNING 并跳过,不会导致导入崩溃,便于排查环境问题。
注意力机制:SDPA 替换 FlashAttention
适配内容中明确「注释flash_attn,使用 SDPA」。转换脚本通过sed将deepencoderv2/sam_vary_sdpa.py中的from flash_attn行注释掉(convert_to_npu.sh L37-L38),配合set_env.sh中的VLLM_ATTENTION_BACKEND=NPU与VLLM_USE_TRITON_FLASH_ATTN=0,使注意力计算走 vLLM-Ascend 的 NPU SDPA 后端,避免 CUDA 版 FlashAttention 在昇腾上不可用的问题。
引擎级参数
ENFORCE_EAGER=True:关闭 CUDA Graph 式捕获,保证 NPU 上算子稳定执行;gpu_memory_utilization=0.85:为 KV Cache 与多模态中间张量预留合理显存余量。
性能测试:benchmark.py 使用与指标解读
单卡性能测试命令:
python benchmark.py --image /path/to/image.jpg --concurrent 1,8,16 --warmup 2 --rounds 3参数说明:
| 参数 | 说明 | 默认值 |
|---|---|---|
--image | 图片文件或目录 | 必填 |
--concurrent | 并发数列表(逗号分隔) | 1,8,16 |
--warmup | 预热轮数 | 2 |
--rounds | 测试轮数 | 5 |
--max-tokens | 最大输出 token | 8192 |
--gpu-mem | 显存利用率 | 0.85 |
--output | 结果输出文件 | benchmark_results.txt |
结合 npu_patch/benchmark.py 源码,脚本的执行逻辑为:
- 图片加载(benchmark.py L77-L115):单文件图片会复制为 N 份以匹配并发数;目录则按
jpg/jpeg/png过滤并循环取图; - 请求构造(benchmark.py L118-L132):调用
DeepseekOCR2Processor.tokenize_with_images(bos=True, eos=True, cropping=CROP_MODE)生成多模态请求,采样参数为temperature=0.0, max_tokens=--max-tokens; - 引擎初始化(benchmark.py L161-L176):以
bfloat16精度加载MODEL_PATH,并通过hf_overrides={"architectures": ["DeepseekOCR2ForCausalLM"]}+trust_remote_code=True注册官方模型类,其余参数全部取自config.py(ENFORCE_EAGER、MAX_MODEL_LEN、SWAP_SPACE等); - 指标采集(benchmark.py L135-L158):每个并发档位依次执行 冷启动测试(cold)→
--warmup轮预热 →--rounds轮正式测试,统计输出吞吐(output_tps = 输出 token / 耗时)与总吞吐(total_tps = 输入+输出 token / 耗时),取均值后打印汇总表并写入--output文件(benchmark.py L264-L306); - 资源回收:每档并发测试结束即
del llm; gc.collect(),避免多档位间显存叠加。
README 记录的单卡性能数据
README「性能数据」一节给出的单卡测试结果如下(数据来源于仓库文档记录,实际数值以所在环境复测为准):
| 并发数 | 输出吞吐 (tokens/s) | 总吞吐 (tokens/s) |
|---|---|---|
| 1 | 40.50 | 96.78 |
| 4 | 106.50 | 292.68 |
| 8 | 212.52 | 584.02 |
| 32 | 413.68 | 1136.81 |
| 64 | 486.62 | 1337.26 |
| 100 | 550.45 | 1512.68 |
可以看到,随着并发数从 1 提升到 100,输出吞吐从约 40 tokens/s 增长到约 550 tokens/s,总吞吐从约 97 tokens/s 增长到约 1513 tokens/s,说明该适配方案在批处理维度具备良好的吞吐扩展性。
项目结构
integration/vllm/deepseek-ocr-2/ ├── convert_to_npu.sh # 一键转换脚本 ├── README.md # 适配说明与部署文档 ├── LICENSE # MIT License └── npu_patch/ ├── deepseek_ocr2_npu.py # NPU MOE 补丁(vllm-ascend fused_experts 替换) ├── set_env.sh # 环境初始化(CANN/ATB/驱动动态库与 vLLM 开关) └── benchmark.py # 单卡并发性能测试脚本故障排除
| 问题 | 解决方案 |
|---|---|
| 指定 NPU 设备 | export ASCEND_RT_VISIBLE_DEVICES=0 |
在多卡机器上运行前,建议显式指定目标 NPU 设备号;其余常见问题可优先检查两点:source set_env.sh是否已执行(决定 CANN 动态库与VLLM_ATTENTION_BACKEND=NPU是否生效)、补丁是否随转换脚本注入(可在目标目录确认存在deepseek_ocr2_npu.py且官方脚本头部包含import deepseek_ocr2_npu)。
许可证与第三方代码
本项目采用 MIT License 开源许可,并包含以下第三方代码(详见 README.md 与 LICENSE):
- SAM(Meta):Apache License 2.0,作为视觉编码器组件;
- DeepSeek-VL2(DeepSeek AI):MIT License,OCR 模型的基础结构来源;
- vLLM:Apache License 2.0,推理框架本体。
使用本方案时需一并遵守上述三方组件的许可约束,且注意适配依赖 vllm-ascend v0.8.5rc1 与 Atlas 800I/T A2 硬件环境,版本或硬件不匹配时需按实际环境重新验证算子兼容性。
【免费下载链接】cann-recipes-infer本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-infer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考