mirror of
https://github.com/opendatalab/MinerU.git
synced 2026-03-27 02:58:54 +07:00
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
@@ -20,13 +20,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: PDF cli
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: dev
|
||||
fetch-depth: 2
|
||||
|
||||
- name: install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@v7
|
||||
|
||||
- name: install&test
|
||||
run: |
|
||||
|
||||
2
.github/workflows/mkdocs.yml
vendored
2
.github/workflows/mkdocs.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: dev
|
||||
- name: Deploy docs
|
||||
|
||||
16
.github/workflows/python-package.yml
vendored
16
.github/workflows/python-package.yml
vendored
@@ -16,13 +16,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
cat mineru/version.py
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
python -m build --wheel
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: wheel-file
|
||||
path: dist/*.whl
|
||||
@@ -121,10 +121,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: wheel-file
|
||||
path: dist
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,6 +16,12 @@ debug/
|
||||
*.ipynb
|
||||
.idea
|
||||
|
||||
# Python build artifacts
|
||||
*.egg-info/
|
||||
build/
|
||||
dist/
|
||||
*.egg
|
||||
|
||||
# vscode history
|
||||
.history
|
||||
|
||||
|
||||
11
README.md
11
README.md
@@ -45,16 +45,21 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
- 2026/01/23 2.7.2 Release
|
||||
- Added support for domestic computing platforms Hygon, Enflame, and Moore Threads. Currently, the officially supported domestic computing platforms include:
|
||||
- 2026/02/13 2.7.4 Release
|
||||
- Added support for domestic computing platforms IluvatarCorex and Cambricon. Currently, the officially supported domestic computing platforms include:
|
||||
- [Ascend](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Ascend/)
|
||||
- [T-Head](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/THead/)
|
||||
- [METAX](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/METAX/)
|
||||
- [Hygon](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Hygon/)
|
||||
- [Enflame](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Enflame/)
|
||||
- [MooreThreads](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/MooreThreads/)
|
||||
- [IluvatarCorex](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/IluvatarCorex/)
|
||||
- [Cambricon](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Cambricon/)
|
||||
- MinerU continues to ensure compatibility with domestic hardware platforms, supporting mainstream chip architectures. With secure and reliable technology, we empower researchers, government, and enterprises to reach new heights in document digitization!
|
||||
- Cross-page table merging optimization, improving merge success rate and merge quality
|
||||
|
||||
- 2026/01/23 2.7.2 Release
|
||||
- Added support for domestic computing platforms Hygon, Enflame, and Moore Threads.
|
||||
- Cross-page table merging optimization, improving merge success rate and merge quality.
|
||||
|
||||
- 2026/01/06 2.7.1 Release
|
||||
- fix bug: #4300
|
||||
|
||||
@@ -45,15 +45,20 @@
|
||||
|
||||
# 更新记录
|
||||
|
||||
- 2026/01/23 2.7.2 发布
|
||||
- 新增国产算力平台海光、燧原、摩尔线程的适配支持,目前已由官方适配并支持的国产算力平台包括:
|
||||
- 2026/01/30 2.7.4 发布
|
||||
- 新增国产算力平台天数智芯、寒武纪的适配支持,目前已由官方适配并支持的国产算力平台包括:
|
||||
- [昇腾 Ascend](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Ascend)
|
||||
- [平头哥 T-Head](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/THead)
|
||||
- [沐曦 METAX](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/METAX)
|
||||
- [海光 Hygon](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Hygon/)
|
||||
- [燧原 Enflame](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Enflame/)
|
||||
- [摩尔线程 MooreThreads](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/MooreThreads/)
|
||||
- [天数智芯 IluvatarCorex](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/IluvatarCorex/)
|
||||
- [寒武纪 Cambricon](https://opendatalab.github.io/MinerU/zh/usage/acceleration_cards/Cambricon/)
|
||||
- MinerU 持续兼容国产硬件平台,支持主流芯片架构。以安全可靠的技术,助力科研、政企用户迈向文档数字化新高度!
|
||||
|
||||
- 2026/01/23 2.7.2 发布
|
||||
- 新增国产算力平台海光、燧原、摩尔线程的适配支持
|
||||
- 跨页表合并优化,提升合并成功率与合并效果
|
||||
|
||||
- 2026/01/06 2.7.1 发布
|
||||
|
||||
27
docker/china/corex.Dockerfile
Normal file
27
docker/china/corex.Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
# Base image containing the vLLM inference environment, requiring amd64(x86-64) CPU + iluvatar GPU.
|
||||
FROM crpi-vofi3w62lkohhxsp.cn-shanghai.personal.cr.aliyuncs.com/opendatalab-mineru/corex:4.4.0_torch2.7.1_vllm0.11.2_py3.10
|
||||
|
||||
|
||||
# Install Noto fonts for Chinese characters
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
fonts-noto-core \
|
||||
fonts-noto-cjk \
|
||||
fontconfig && \
|
||||
fc-cache -fv && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install mineru latest
|
||||
RUN python3 -m pip install -U pip -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
python3 -m pip install 'mineru[core]>=2.7.4' \
|
||||
numpy==1.26.4 \
|
||||
opencv-python==4.11.0.86 \
|
||||
-i https://mirrors.aliyun.com/pypi/simple && \
|
||||
python3 -m pip cache purge
|
||||
|
||||
# Download models and update the configuration file
|
||||
RUN /bin/bash -c "mineru-models-download -s modelscope -m all"
|
||||
|
||||
# Set the entry point to activate the virtual environment and run the command line tool
|
||||
ENTRYPOINT ["/bin/bash", "-c", "export MINERU_MODEL_SOURCE=local && exec \"$@\"", "--"]
|
||||
@@ -2,7 +2,7 @@
|
||||
FROM harbor.sourcefind.cn:5443/dcu/admin/base/vllm:0.9.2-ubuntu22.04-dtk25.04.2-1226-das1.7-py3.10-20251226
|
||||
|
||||
|
||||
# Install libgl for opencv support & Noto fonts for Chinese characters
|
||||
# Install Noto fonts for Chinese characters
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
fonts-noto-core \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
FROM crpi-vofi3w62lkohhxsp.cn-shanghai.personal.cr.aliyuncs.com/opendatalab-mineru/gcu:docker_images_topsrider_i3x_3.6.20260106_vllm0.11_pytorch2.8.0
|
||||
|
||||
|
||||
# Install libgl for opencv support & Noto fonts for Chinese characters
|
||||
# Install Noto fonts for Chinese characters
|
||||
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ noble main restricted universe multiverse\n\
|
||||
deb http://mirrors.aliyun.com/ubuntu/ noble-updates main restricted universe multiverse\n\
|
||||
deb http://mirrors.aliyun.com/ubuntu/ noble-backports main restricted universe multiverse\n\
|
||||
|
||||
42
docker/china/mlu.Dockerfile
Normal file
42
docker/china/mlu.Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# 基础镜像配置 vLLM 或 LMDeploy ,请根据实际需要选择其中一个,要求 amd64(x86-64) CPU + Cambricon MLU.
|
||||
# Base image containing the LMDEPLOY inference environment, requiring amd64(x86-64) CPU + Cambricon MLU.
|
||||
FROM crpi-4crprmm5baj1v8iv.cn-hangzhou.personal.cr.aliyuncs.com/lmdeploy_dlinfer/camb:qwen_vl2.5
|
||||
ARG BACKEND=lmdeploy
|
||||
# Base image containing the vLLM inference environment, requiring amd64(x86-64) CPU + Cambricon MLU.
|
||||
# FROM crpi-vofi3w62lkohhxsp.cn-shanghai.personal.cr.aliyuncs.com/opendatalab-mineru/mlu:vllm0.8.3-torch2.6.0-torchmlu1.26.1-ubuntu22.04-py310
|
||||
# ARG BACKEND=vllm
|
||||
|
||||
# Install Noto fonts for Chinese characters
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
fonts-noto-core \
|
||||
fonts-noto-cjk \
|
||||
fontconfig && \
|
||||
fc-cache -fv && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install mineru latest
|
||||
RUN /bin/bash -c '\
|
||||
if [ "$BACKEND" = "vllm" ]; then \
|
||||
source /torch/venv3/pytorch_infer/bin/activate; \
|
||||
fi && \
|
||||
python3 -m pip install -U pip -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
python3 -m pip install "mineru[core]>=2.7.4" \
|
||||
numpy==1.26.4 \
|
||||
opencv-python==4.11.0.86 \
|
||||
-i https://mirrors.aliyun.com/pypi/simple && \
|
||||
python3 -m pip install $(if [ "$BACKEND" = "lmdeploy" ]; then echo "accelerate==1.2.0"; else echo "transformers==4.50.3"; fi) && \
|
||||
python3 -m pip cache purge'
|
||||
|
||||
# Download models and update the configuration file
|
||||
RUN /bin/bash -c '\
|
||||
if [ "$BACKEND" = "vllm" ]; then \
|
||||
source /torch/venv3/pytorch_infer/bin/activate; \
|
||||
fi && \
|
||||
mineru-models-download -s modelscope -m all'
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Set the entry point to activate the virtual environment and run the command line tool
|
||||
ENTRYPOINT ["/bin/bash", "-c", "export MINERU_MODEL_SOURCE=local && exec \"$@\"", "--"]
|
||||
@@ -86,7 +86,11 @@ docker run -u root --name mineru_docker --privileged=true \
|
||||
您也可以直接通过替换`/bin/bash`为服务启动命令来启动MinerU服务,详细说明请参考[通过命令启动服务](https://opendatalab.github.io/MinerU/zh/usage/quick_usage/#apiwebuihttp-clientserver)。
|
||||
|
||||
>[!NOTE]
|
||||
> 由于310p加速卡不支持bf16精度,因此在使用该加速卡时,执行任意与`vllm`相关命令需追加`--enforce-eager --dtype float16`参数。
|
||||
> 由于310p加速卡不支持图模式与bf16精度,因此在使用该加速卡时,执行任意与`vllm`相关命令需追加`--enforce-eager --dtype float16`参数。
|
||||
> 例如:
|
||||
> ```bash
|
||||
> mineru-openai-server --port 30000 --enforce-eager --dtype float16
|
||||
> ```
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
|
||||
@@ -1,253 +1,169 @@
|
||||
# MinerU
|
||||
## 1. 环境准备
|
||||
容器启动方式见第3节
|
||||
### 1.1 获取代码
|
||||
## 1. 测试平台
|
||||
以下为本指南测试使用的平台信息,供参考:
|
||||
```
|
||||
git clone https://github.com/opendatalab/MinerU.git
|
||||
git checkout fa1149cd4abf9db5e0f13e4e074cdb568be189f4
|
||||
```
|
||||
### 1.2 安装依赖
|
||||
```
|
||||
source /torch/venv3/pytorch_infer/bin/activate
|
||||
pip install accelerate==1.11.0 doclayout_yolo==0.0.4 thop==0.1.1.post2209072238 ultralytics-thop==2.0.18 ultralytics==8.3.228
|
||||
# requirements_check.txt具体内容在下面
|
||||
pip install -r requirements_check.txt
|
||||
cd MinerU
|
||||
pip install -e .[core] --no-deps
|
||||
```
|
||||
requirements_check.txt
|
||||
```
|
||||
# triton==3.0.0+mlu1.3.1
|
||||
# torch==2.5.0+cpu
|
||||
# torchvision==0.20.0+cpu
|
||||
|
||||
|
||||
# === 1. 已安装且版本相同 ===
|
||||
# (这些包已满足要求, 无需操作)
|
||||
|
||||
|
||||
# === 2. 已安装但版本不同 ===
|
||||
# (运行 pip install -r 将强制更新到左侧的目标版本)
|
||||
# accelerate==1.11.0 # 0.33.0
|
||||
beautifulsoup4==4.14.2 # 4.12.3
|
||||
cffi==2.0.0 # 1.17.1
|
||||
huggingface-hub==0.36.0 # 0.25.2
|
||||
jiter==0.12.0 # 0.8.2
|
||||
openai==2.8.0 # 1.59.7
|
||||
pillow==11.3.0 # 10.4.0
|
||||
sympy==1.14.0 # 1.13.1
|
||||
tokenizers==0.22.1 # 0.21.0
|
||||
# torch==2.9.1 # 2.5.0+cpu
|
||||
# torchvision==0.24.1 # 0.20.0+cpu
|
||||
transformers==4.57.1 # 4.48.0
|
||||
# triton==3.5.1 # 3.0.0+mlu1.3.1
|
||||
typing-extensions==4.15.0 # 4.12.2
|
||||
|
||||
# === 3. 未安装 ===
|
||||
# (运行 pip install -r 将安装这些包)
|
||||
aiofiles==24.1.0
|
||||
albucore==0.0.24
|
||||
albumentations==2.0.8
|
||||
antlr4-python3-runtime==4.9.3
|
||||
brotli==1.2.0
|
||||
coloredlogs==15.0.1
|
||||
colorlog==6.10.1
|
||||
cryptography==46.0.3
|
||||
# doclayout_yolo==0.0.4
|
||||
fast-langdetect==0.2.5
|
||||
fasttext-predict==0.9.2.4
|
||||
ffmpy==1.0.0
|
||||
flatbuffers==25.9.23
|
||||
ftfy==6.3.1
|
||||
gradio-client==1.13.3
|
||||
gradio-pdf==0.0.22
|
||||
gradio==5.49.1
|
||||
groovy==0.1.2
|
||||
hf-xet==1.2.0
|
||||
httpx-retries==0.4.5
|
||||
humanfriendly==10.0
|
||||
imageio==2.37.2
|
||||
json-repair==0.53.0
|
||||
magika==0.6.3
|
||||
markdown-it-py==4.0.0
|
||||
mdurl==0.1.2
|
||||
mineru-vl-utils==0.1.15
|
||||
mineru==2.6.4
|
||||
modelscope==1.31.0
|
||||
# nvidia-cublas-cu12==12.8.4.1
|
||||
# nvidia-cuda-cupti-cu12==12.8.90
|
||||
# nvidia-cuda-nvrtc-cu12==12.8.93
|
||||
# nvidia-cuda-runtime-cu12==12.8.90
|
||||
# nvidia-cudnn-cu12==9.10.2.21
|
||||
# nvidia-cufft-cu12==11.3.3.83
|
||||
# nvidia-cufile-cu12==1.13.1.3
|
||||
# nvidia-curand-cu12==10.3.9.90
|
||||
# nvidia-cusolver-cu12==11.7.3.90
|
||||
# nvidia-cusparse-cu12==12.5.8.93
|
||||
# nvidia-cusparselt-cu12==0.7.1
|
||||
# nvidia-nccl-cu12==2.27.5
|
||||
# nvidia-nvjitlink-cu12==12.8.93
|
||||
# nvidia-nvshmem-cu12==3.3.20
|
||||
# nvidia-nvtx-cu12==12.8.90
|
||||
omegaconf==2.3.0
|
||||
onnxruntime==1.23.2
|
||||
orjson==3.11.4
|
||||
pdfminer.six==20250506
|
||||
pdftext==0.6.3
|
||||
polars-runtime-32==1.35.2
|
||||
polars==1.35.2
|
||||
pyclipper==1.3.0.post6
|
||||
pydantic-settings==2.12.0
|
||||
pydub==0.25.1
|
||||
pypdf==6.2.0
|
||||
pypdfium2==4.30.0
|
||||
python-multipart==0.0.20
|
||||
reportlab==4.4.4
|
||||
rich==14.2.0
|
||||
robust-downloader==0.0.2
|
||||
ruff==0.14.5
|
||||
safehttpx==0.1.7
|
||||
scikit-image==0.25.2
|
||||
seaborn==0.13.2
|
||||
semantic-version==2.10.0
|
||||
shapely==2.1.2
|
||||
shellingham==1.5.4
|
||||
simsimd==6.5.3
|
||||
stringzilla==4.2.3
|
||||
# thop==0.1.1.post2209072238
|
||||
tifffile==2025.5.10
|
||||
typer==0.20.0
|
||||
typing-inspection==0.4.2
|
||||
# ultralytics-thop==2.0.18
|
||||
# ultralytics==8.3.228
|
||||
```
|
||||
### 1.3 修改代码
|
||||
/raid_data/home/yqk/mineru-251114/MinerU/mineru/backend/pipeline/pipeline_analyze.py, line 1
|
||||
添加代码
|
||||
```
|
||||
# 添加MLU支持
|
||||
import torch_mlu.utils.gpu_migration
|
||||
# 高版本镜像为
|
||||
# import torch.mlu.utils.gpu_migration
|
||||
os: Ubuntu 22.04.5 LTS
|
||||
cpu: Hygon Hygon C86 7490
|
||||
gcu: MLU590-M9D
|
||||
driver: v6.2.11
|
||||
docker: 28.3.0
|
||||
```
|
||||
|
||||
## 2. 使用方法
|
||||
```
|
||||
export HF_ENDPOINT=https://hf-mirror.com
|
||||
mineru-api --host 0.0.0.0 --port 8009
|
||||
## 2. 环境准备
|
||||
|
||||
>[!NOTE]
|
||||
>Ascend加速卡支持使用`lmdeploy`或`vllm`进行VLM模型推理加速。请根据实际需求选择安装和使用其中之一:
|
||||
|
||||
### 2.1 使用 Dockerfile 构建镜像 (lmdeploy)
|
||||
|
||||
```bash
|
||||
wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/china/mlu.Dockerfile
|
||||
docker build --network=host -t mineru:mlu-lmdeploy-latest -f mlu.Dockerfile .
|
||||
```
|
||||
|
||||
## 3. 其他
|
||||
### 2.2 使用 Dockerfile 构建镜像 (vllm)
|
||||
|
||||
### 3.1 Dify插件配置问题
|
||||
给Dify的MinerU插件使用时,需将Dify的.env文件中FILES_URL设置为http://{ip}:{dify的网页访问端口}。
|
||||
根据网上找到的很多回答可能是要暴露5001,并将FILES_URL设置为http://{ip}:5001,并暴露5001端口,但其实设置为dify的网页访问端口即可。
|
||||
|
||||
### 3.2 容器启动方式
|
||||
|
||||
```
|
||||
export MY_CONTAINER="[容器名称]"
|
||||
num=`docker ps -a|grep "$MY_CONTAINER" | wc -l`
|
||||
echo $num
|
||||
echo $MY_CONTAINER
|
||||
if [ 0 -eq $num ];then
|
||||
docker run -d \
|
||||
--privileged \
|
||||
--pid=host \
|
||||
--net=host \
|
||||
--shm-size 64g \
|
||||
--device /dev/cambricon_dev0 \
|
||||
--device /dev/cambricon_ipcm0 \
|
||||
--device /dev/cambricon_ctl \
|
||||
--name $MY_CONTAINER \
|
||||
-v [/path/to/your/data:/path/to/your/data] \
|
||||
-v /usr/bin/cnmon:/usr/bin/cnmon \
|
||||
[镜像名称] \
|
||||
sleep infinity
|
||||
docker exec -ti $MY_CONTAINER /bin/bash
|
||||
else
|
||||
docker start $MY_CONTAINER
|
||||
docker exec -ti $MY_CONTAINER /bin/bash
|
||||
fi
|
||||
```bash
|
||||
wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/china/mlu.Dockerfile
|
||||
# 将基础镜像从 lmdeploy 切换为 vllm
|
||||
sed -i -e '3,4s/^/# /' -e '6,7s/^# //' mlu.Dockerfile
|
||||
docker build --network=host -t mineru:mlu-vllm-latest -f mlu.Dockerfile .
|
||||
```
|
||||
|
||||
### 3.3 将上面的过程进行打包
|
||||
## 3. 启动 Docker 容器
|
||||
|
||||
准备好前面的requirements_check.txt
|
||||
|
||||
Dockerfile
|
||||
|
||||
```
|
||||
# 1. 使用指定的基础镜像
|
||||
FROM cambricon-base/pytorch:v25.01-torch2.5.0-torchmlu1.24.1-ubuntu22.04-py310
|
||||
|
||||
# 2. 设置环境变量
|
||||
ENV HF_ENDPOINT=https://hf-mirror.com
|
||||
|
||||
# 3. 定义 venv_pip 路径以便复用
|
||||
# 基础镜像中的虚拟环境路径
|
||||
ARG VENV_PIP=/torch/venv3/pytorch_infer/bin/pip
|
||||
|
||||
# 4. 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 5. 安装 git (基础镜像可能不包含)
|
||||
RUN apt-get update && apt-get install -y git && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 6. 复制 requirements_check.txt 到镜像中
|
||||
# (这个文件需要您在宿主机上和 Dockerfile 放在同一目录下)
|
||||
COPY requirements_check.txt .
|
||||
|
||||
# 7. 步骤 1.1 & 1.2: 获取代码并安装所有依赖
|
||||
# 在一个 RUN 层中执行所有安装,以优化镜像大小
|
||||
RUN \
|
||||
# 1.1 获取代码
|
||||
echo "Cloning MinerU repository..." && \
|
||||
git clone https://gh-proxy.org/https://github.com/opendatalab/MinerU.git && \
|
||||
cd MinerU && \
|
||||
git checkout fa1149cd4abf9db5e0f13e4e074cdb568be189f4 && \
|
||||
cd .. && \
|
||||
\
|
||||
# 1.2 安装依赖
|
||||
# 第1个pip install (来自您的步骤)
|
||||
echo "Installing initial dependencies..." && \
|
||||
${VENV_PIP} install accelerate==1.11.0 doclayout_yolo==0.0.4 thop==0.1.1.post2209072238 ultralytics-thop==2.0.18 ultralytics==8.3.228 && \
|
||||
\
|
||||
# 第2个pip install (来自 requirements_check.txt)
|
||||
echo "Installing dependencies from requirements_check.txt..." && \
|
||||
# 注意:基础镜像已包含 torch 和 triton,requirements_check.txt 中的注释行会被 pip 自动忽略
|
||||
${VENV_PIP} install -r requirements_check.txt && \
|
||||
\
|
||||
# 第3个pip install (本地安装 MinerU)
|
||||
echo "Installing MinerU in editable mode..." && \
|
||||
cd MinerU && \
|
||||
${VENV_PIP} install -e .[core] --no-deps
|
||||
|
||||
# 8. 步骤 1.3: 修改代码
|
||||
# 将 MLU 支持代码添加到指定文件的开头
|
||||
RUN echo "Applying MLU patch to pipeline_analyze.py..." && \
|
||||
sed -i '1i# 添加MLU支持\nimport torch_mlu.utils.gpu_migration\n# 高版本镜像为\n# import torch.mlu.utils.gpu_migration\n' \
|
||||
/app/MinerU/mineru/backend/pipeline/pipeline_analyze.py
|
||||
```bash
|
||||
docker run --name mineru_docker \
|
||||
--privileged \
|
||||
--ipc=host \
|
||||
--network=host \
|
||||
--cap-add SYS_PTRACE \
|
||||
--device=/dev/mem \
|
||||
--device=/dev/dri \
|
||||
--device=/dev/infiniband \
|
||||
--device=/dev/cambricon_ctl \
|
||||
--device=/dev/cambricon_dev0 \
|
||||
--device=/dev/cambricon_dev1 \
|
||||
--device=/dev/cambricon_dev2 \
|
||||
--device=/dev/cambricon_dev3 \
|
||||
--device=/dev/cambricon_dev4 \
|
||||
--device=/dev/cambricon_dev5 \
|
||||
--device=/dev/cambricon_dev6 \
|
||||
--device=/dev/cambricon_dev7 \
|
||||
--group-add video \
|
||||
--shm-size=400g \
|
||||
--ulimit memlock=-1 \
|
||||
--security-opt seccomp=unconfined \
|
||||
--security-opt apparmor=unconfined \
|
||||
-e MINERU_MODEL_SOURCE=local \
|
||||
-e MINERU_LMDEPLOY_DEVICE=camb \
|
||||
-it mineru:mlu-lmdeploy-latest \
|
||||
/bin/bash
|
||||
```
|
||||
|
||||
该镜像的启动
|
||||
>[!TIP]
|
||||
> 请根据实际情况选择使用`vllm`或`lmdeploy`版本的镜像,如需使用`vllm`,请执行以下操作:
|
||||
> - 替换上述命令中的`mineru:mlu-lmdeploy-latest`为`mineru:mlu-vllm-latest`
|
||||
> - 进入容器后,通过以下命令切换venv环境:
|
||||
> ```bash
|
||||
> source /torch/venv3/pytorch_infer/bin/activate
|
||||
> ```
|
||||
> - 切换成功后,您可以在命令行前看到`(pytorch_infer)`的标识,这表示您已成功进入`vllm`的虚拟环境。
|
||||
|
||||
```
|
||||
docker run -d --restart=always \
|
||||
--privileged \
|
||||
--pid=host \
|
||||
--net=host \
|
||||
--shm-size 64g \
|
||||
--device /dev/cambricon_dev0 \
|
||||
--device /dev/cambricon_ipcm0 \
|
||||
--device /dev/cambricon_ctl \
|
||||
--name mineru_service \
|
||||
mineru-mlu:latest \
|
||||
/torch/venv3/pytorch_infer/bin/python /app/MinerU/mineru/cli/fast_api.py --host 0.0.0.0 --port 8009
|
||||
```
|
||||
执行该命令后,您将进入到Docker容器的交互式终端,您可以直接在容器内运行MinerU相关命令来使用MinerU的功能。
|
||||
您也可以直接通过替换`/bin/bash`为服务启动命令来启动MinerU服务,详细说明请参考[通过命令启动服务](https://opendatalab.github.io/MinerU/zh/usage/quick_usage/#apiwebuihttp-clientserver)。
|
||||
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
>[!NOTE]
|
||||
> **兼容性说明**:由于寒武纪(Cambricon)目前对 vLLM v1 引擎的支持尚待完善,MinerU 现阶段采用 v0 引擎作为适配方案。
|
||||
> 受此限制,vLLM 的异步引擎(Async Engine)功能存在兼容性问题,可能导致部分使用场景无法正常运行。
|
||||
> 我们将持续跟进寒武纪对 vLLM v1 引擎的支持进展,并及时在 MinerU 中进行相应的适配与优化。
|
||||
|
||||
不同环境下,MinerU对Cambricon加速卡的支持情况如下表所示:
|
||||
|
||||
>[!TIP]
|
||||
> - `lmdeploy`黄灯问题为不能批量输出文件夹,单文件输入正常
|
||||
> - `vllm`黄灯问题为在精度未对齐,在部分场景下可能出现预期外结果。
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" colspan="2">使用场景</th>
|
||||
<th colspan="2">容器环境</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>vllm</th>
|
||||
<th>lmdeploy</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="3">命令行工具(mineru)</td>
|
||||
<td>pipeline</td>
|
||||
<td>🟢</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-auto-engine</td>
|
||||
<td>🟡</td>
|
||||
<td>🟡</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-http-client</td>
|
||||
<td>🟡</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">fastapi服务(mineru-api)</td>
|
||||
<td>pipeline</td>
|
||||
<td>🟢</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-auto-engine</td>
|
||||
<td>🔴</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-http-client</td>
|
||||
<td>🟡</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">gradio界面(mineru-gradio)</td>
|
||||
<td>pipeline</td>
|
||||
<td>🟢</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-auto-engine</td>
|
||||
<td>🔴</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-http-client</td>
|
||||
<td>🟡</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">openai-server服务(mineru-openai-server)</td>
|
||||
<td>🟡</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">数据并行 (--data-parallel-size/--dp)</td>
|
||||
<td>🔴</td>
|
||||
<td>🔴</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
注:
|
||||
🟢: 支持,运行较稳定,精度与Nvidia GPU基本一致
|
||||
🟡: 支持但较不稳定,在某些场景下可能出现异常,或精度存在一定差异
|
||||
🔴: 不支持,无法运行,或精度存在较大差异
|
||||
|
||||
>[!TIP]
|
||||
>Cambricon加速卡指定可用加速卡的方式与NVIDIA GPU类似,请参考[使用指定GPU设备](https://opendatalab.github.io/MinerU/zh/usage/advanced_cli_parameters/#cuda_visible_devices)章节说明,
|
||||
>将环境变量`CUDA_VISIBLE_DEVICES`替换为`MLU_VISIBLE_DEVICES`即可。
|
||||
122
docs/zh/usage/acceleration_cards/IluvatarCorex.md
Normal file
122
docs/zh/usage/acceleration_cards/IluvatarCorex.md
Normal file
@@ -0,0 +1,122 @@
|
||||
## 1. 测试平台
|
||||
以下为本指南测试使用的平台信息,供参考:
|
||||
```
|
||||
os: Ubuntu 22.04.5 LTS
|
||||
cpu: Intel x86-64
|
||||
gcu: Iluvatar BI-V150
|
||||
driver: 4.4.0
|
||||
docker: 28.1.1
|
||||
```
|
||||
|
||||
## 2. 环境准备
|
||||
|
||||
### 2.1 使用 Dockerfile 构建镜像
|
||||
|
||||
```bash
|
||||
wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/china/corex.Dockerfile
|
||||
docker build --network=host -t mineru:corex-vllm-latest -f corex.Dockerfile .
|
||||
```
|
||||
|
||||
|
||||
## 3. 启动 Docker 容器
|
||||
|
||||
```bash
|
||||
docker run --name mineru_docker \
|
||||
-v /usr/src:/usr/src \
|
||||
-v /lib/modules:/lib/modules \
|
||||
-v /dev:/dev \
|
||||
--privileged \
|
||||
--cap-add=ALL \
|
||||
--pid=host \
|
||||
--group-add video \
|
||||
--network=host \
|
||||
--shm-size '400gb' \
|
||||
--ulimit memlock=-1 \
|
||||
--security-opt seccomp=unconfined \
|
||||
--security-opt apparmor=unconfined \
|
||||
-e VLLM_ENFORCE_CUDA_GRAPH=1 \
|
||||
-e MINERU_MODEL_SOURCE=local \
|
||||
-e MINERU_LMDEPLOY_DEVICE=corex \
|
||||
-it mineru:corex-vllm-latest \
|
||||
/bin/bash
|
||||
```
|
||||
|
||||
执行该命令后,您将进入到Docker容器的交互式终端,您可以直接在容器内运行MinerU相关命令来使用MinerU的功能。
|
||||
您也可以直接通过替换`/bin/bash`为服务启动命令来启动MinerU服务,详细说明请参考[通过命令启动服务](https://opendatalab.github.io/MinerU/zh/usage/quick_usage/#apiwebuihttp-clientserver)。
|
||||
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
>[!TIP]
|
||||
>目前Iluvatar方案使用vllm作为推理引擎时,可能出现服务停止后显存无法正常释放的问题,如果遇到该问题,请重启Docker容器以释放显存。
|
||||
|
||||
不同环境下,MinerU对Iluvatar加速卡的支持情况如下表所示:
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" colspan="2">使用场景</th>
|
||||
<th colspan="2">容器环境</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>vllm</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="3">命令行工具(mineru)</td>
|
||||
<td>pipeline</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-auto-engine</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-http-client</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">fastapi服务(mineru-api)</td>
|
||||
<td>pipeline</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-auto-engine</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-http-client</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">gradio界面(mineru-gradio)</td>
|
||||
<td>pipeline</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-auto-engine</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><vlm/hybrid>-http-client</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">openai-server服务(mineru-openai-server)</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">数据并行 (--data-parallel-size)</td>
|
||||
<td>🟢</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
注:
|
||||
🟢: 支持,运行较稳定,精度与Nvidia GPU基本一致
|
||||
🟡: 支持但较不稳定,在某些场景下可能出现异常,或精度存在一定差异
|
||||
🔴: 不支持,无法运行,或精度存在较大差异
|
||||
|
||||
>[!TIP]
|
||||
>Iluvatar加速卡指定可用加速卡的方式与NVIDIA GPU类似,请参考[使用指定GPU设备](https://opendatalab.github.io/MinerU/zh/usage/advanced_cli_parameters/#cuda_visible_devices)章节说明
|
||||
@@ -15,9 +15,10 @@
|
||||
* [海光 Hygon](acceleration_cards/Hygon.md) 🚀
|
||||
* [燧原 Enflame](acceleration_cards/Enflame.md) 🚀
|
||||
* [摩尔线程 MooreThreads](acceleration_cards/MooreThreads.md) 🚀
|
||||
* [天数智芯 IluvatarCorex](acceleration_cards/IluvatarCorex.md) 🚀
|
||||
* [寒武纪 Cambricon](acceleration_cards/Cambricon.md) 🚀
|
||||
* [AMD](acceleration_cards/AMD.md) [#3662](https://github.com/opendatalab/MinerU/discussions/3662) ❤️
|
||||
* [太初元碁 Tecorigin](acceleration_cards/Tecorigin.md) [#3767](https://github.com/opendatalab/MinerU/pull/3767) ❤️
|
||||
* [寒武纪 Cambricon](acceleration_cards/Cambricon.md) [#4004](https://github.com/opendatalab/MinerU/discussions/4004) ❤️
|
||||
* [瀚博 VastAI](acceleration_cards/VastAI.md) [#4237](https://github.com/opendatalab/MinerU/discussions/4237)❤️
|
||||
- 插件与生态
|
||||
* [Cherry Studio](plugin/Cherry_Studio.md)
|
||||
|
||||
@@ -297,7 +297,14 @@ def ocr_det_batch_setting(device):
|
||||
# 检测torch的版本号
|
||||
import torch
|
||||
from packaging import version
|
||||
if version.parse(torch.__version__) >= version.parse("2.8.0") or str(device).startswith('mps'):
|
||||
|
||||
device_type = os.getenv("MINERU_LMDEPLOY_DEVICE", "")
|
||||
|
||||
if (
|
||||
version.parse(torch.__version__) >= version.parse("2.8.0")
|
||||
or str(device).startswith('mps')
|
||||
or device_type.lower() in ["corex"]
|
||||
):
|
||||
enable_ocr_det_batch = False
|
||||
else:
|
||||
enable_ocr_det_batch = True
|
||||
|
||||
@@ -193,7 +193,12 @@ def batch_image_analyze(
|
||||
# 检测torch的版本号
|
||||
import torch
|
||||
from packaging import version
|
||||
if version.parse(torch.__version__) >= version.parse("2.8.0") or str(device).startswith('mps'):
|
||||
device_type = os.getenv("MINERU_LMDEPLOY_DEVICE", "")
|
||||
if (
|
||||
version.parse(torch.__version__) >= version.parse("2.8.0")
|
||||
or str(device).startswith('mps')
|
||||
or device_type.lower() in ["corex"]
|
||||
):
|
||||
enable_ocr_det_batch = False
|
||||
else:
|
||||
enable_ocr_det_batch = True
|
||||
|
||||
@@ -22,6 +22,8 @@ def enable_custom_logits_processors() -> bool:
|
||||
compute_capability = "8.0"
|
||||
elif hasattr(torch, 'musa') and torch.musa.is_available():
|
||||
compute_capability = "8.0"
|
||||
elif hasattr(torch, 'mlu') and torch.mlu.is_available():
|
||||
compute_capability = "8.0"
|
||||
else:
|
||||
logger.info("CUDA not available, disabling custom_logits_processors")
|
||||
return False
|
||||
|
||||
@@ -101,20 +101,27 @@ class ModelSingleton:
|
||||
except ImportError:
|
||||
raise ImportError("Please install vllm to use the vllm-engine backend.")
|
||||
|
||||
"""
|
||||
# musa vllm v1 引擎特殊配置
|
||||
device = get_device()
|
||||
if device.startswith("musa"):
|
||||
import torch
|
||||
if torch.musa.is_available():
|
||||
compilation_config = {
|
||||
"cudagraph_capture_sizes": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30],
|
||||
"simple_cuda_graph": True
|
||||
}
|
||||
block_size = 32
|
||||
kwargs["compilation_config"] = compilation_config
|
||||
kwargs["block_size"] = block_size
|
||||
"""
|
||||
# device = get_device()
|
||||
# if device_type.startswith("musa"):
|
||||
# import torch
|
||||
# if torch.musa.is_available():
|
||||
# compilation_config = {
|
||||
# "cudagraph_capture_sizes": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30],
|
||||
# "simple_cuda_graph": True
|
||||
# }
|
||||
# block_size = 32
|
||||
# kwargs["compilation_config"] = compilation_config
|
||||
# kwargs["block_size"] = block_size
|
||||
|
||||
# corex vllm v1 引擎特殊配置
|
||||
device_type = os.getenv("MINERU_LMDEPLOY_DEVICE", "")
|
||||
if device_type.lower() == "corex":
|
||||
compilation_config = {
|
||||
"cudagraph_mode": "FULL_DECODE_ONLY",
|
||||
"level": 0
|
||||
}
|
||||
kwargs["compilation_config"] = compilation_config
|
||||
|
||||
if "compilation_config" in kwargs:
|
||||
if isinstance(kwargs["compilation_config"], str):
|
||||
@@ -141,20 +148,28 @@ class ModelSingleton:
|
||||
except ImportError:
|
||||
raise ImportError("Please install vllm to use the vllm-async-engine backend.")
|
||||
|
||||
"""
|
||||
|
||||
# musa vllm v1 引擎特殊配置
|
||||
device = get_device()
|
||||
if device.startswith("musa"):
|
||||
import torch
|
||||
if torch.musa.is_available():
|
||||
compilation_config = CompilationConfig(
|
||||
cudagraph_capture_sizes=[1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30],
|
||||
simple_cuda_graph=True
|
||||
)
|
||||
block_size = 32
|
||||
kwargs["compilation_config"] = compilation_config
|
||||
kwargs["block_size"] = block_size
|
||||
"""
|
||||
# device = get_device()
|
||||
# if device.startswith("musa"):
|
||||
# import torch
|
||||
# if torch.musa.is_available():
|
||||
# compilation_config = CompilationConfig(
|
||||
# cudagraph_capture_sizes=[1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30],
|
||||
# simple_cuda_graph=True
|
||||
# )
|
||||
# block_size = 32
|
||||
# kwargs["compilation_config"] = compilation_config
|
||||
# kwargs["block_size"] = block_size
|
||||
|
||||
# corex vllm v1 引擎特殊配置
|
||||
device_type = os.getenv("MINERU_LMDEPLOY_DEVICE", "")
|
||||
if device_type.lower() == "corex":
|
||||
compilation_config = CompilationConfig(
|
||||
cudagraph_mode="FULL_DECODE_ONLY",
|
||||
level=0
|
||||
)
|
||||
kwargs["compilation_config"] = compilation_config
|
||||
|
||||
if "compilation_config" in kwargs:
|
||||
if isinstance(kwargs["compilation_config"], dict):
|
||||
|
||||
@@ -7,12 +7,12 @@ import asyncio
|
||||
import uvicorn
|
||||
import click
|
||||
import zipfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import glob
|
||||
from fastapi import Depends, FastAPI, HTTPException, UploadFile, File, Form
|
||||
from fastapi import Depends, FastAPI, HTTPException, UploadFile, File, Form, BackgroundTasks
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.responses import JSONResponse, FileResponse
|
||||
from starlette.background import BackgroundTask
|
||||
from typing import List, Optional
|
||||
from loguru import logger
|
||||
|
||||
@@ -30,23 +30,30 @@ from mineru.version import __version__
|
||||
# 并发控制器
|
||||
_request_semaphore: Optional[asyncio.Semaphore] = None
|
||||
|
||||
|
||||
# 并发控制依赖函数
|
||||
async def limit_concurrency():
|
||||
if _request_semaphore is not None:
|
||||
if _request_semaphore.locked():
|
||||
# 检查信号量是否已用尽,如果是则拒绝请求
|
||||
if _request_semaphore._value == 0:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Server is at maximum capacity: {os.getenv('MINERU_API_MAX_CONCURRENT_REQUESTS', 'unset')}. Please try again later."
|
||||
detail=f"Server is at maximum capacity: {os.getenv('MINERU_API_MAX_CONCURRENT_REQUESTS', 'unset')}. Please try again later.",
|
||||
)
|
||||
async with _request_semaphore:
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
def create_app():
|
||||
# By default, the OpenAPI documentation endpoints (openapi_url, docs_url, redoc_url) are enabled.
|
||||
# To disable the FastAPI docs and schema endpoints, set the environment variable MINERU_API_ENABLE_FASTAPI_DOCS=0.
|
||||
enable_docs = str(os.getenv("MINERU_API_ENABLE_FASTAPI_DOCS", "1")).lower() in ("1", "true", "yes")
|
||||
enable_docs = str(os.getenv("MINERU_API_ENABLE_FASTAPI_DOCS", "1")).lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
)
|
||||
app = FastAPI(
|
||||
openapi_url="/openapi.json" if enable_docs else None,
|
||||
docs_url="/docs" if enable_docs else None,
|
||||
@@ -56,7 +63,9 @@ def create_app():
|
||||
# 初始化并发控制器:从环境变量MINERU_API_MAX_CONCURRENT_REQUESTS读取
|
||||
global _request_semaphore
|
||||
try:
|
||||
max_concurrent_requests = int(os.getenv("MINERU_API_MAX_CONCURRENT_REQUESTS", "0"))
|
||||
max_concurrent_requests = int(
|
||||
os.getenv("MINERU_API_MAX_CONCURRENT_REQUESTS", "0")
|
||||
)
|
||||
except ValueError:
|
||||
max_concurrent_requests = 0
|
||||
|
||||
@@ -67,6 +76,7 @@ def create_app():
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
@@ -76,27 +86,34 @@ def sanitize_filename(filename: str) -> str:
|
||||
移除路径遍历字符, 保留 Unicode 字母、数字、._-
|
||||
禁止隐藏文件
|
||||
"""
|
||||
sanitized = re.sub(r'[/\\\.]{2,}|[/\\]', '', filename)
|
||||
sanitized = re.sub(r'[^\w.-]', '_', sanitized, flags=re.UNICODE)
|
||||
if sanitized.startswith('.'):
|
||||
sanitized = '_' + sanitized[1:]
|
||||
return sanitized or 'unnamed'
|
||||
sanitized = re.sub(r"[/\\.]{2,}|[/\\]", "", filename)
|
||||
sanitized = re.sub(r"[^\w.-]", "_", sanitized, flags=re.UNICODE)
|
||||
if sanitized.startswith("."):
|
||||
sanitized = "_" + sanitized[1:]
|
||||
return sanitized or "unnamed"
|
||||
|
||||
|
||||
def cleanup_file(file_path: str) -> None:
|
||||
"""清理临时 zip 文件"""
|
||||
"""清理临时文件或目录"""
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
elif os.path.isdir(file_path):
|
||||
shutil.rmtree(file_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"fail clean file {file_path}: {e}")
|
||||
|
||||
|
||||
def encode_image(image_path: str) -> str:
|
||||
"""Encode image using base64"""
|
||||
with open(image_path, "rb") as f:
|
||||
return b64encode(f.read()).decode()
|
||||
|
||||
|
||||
def get_infer_result(file_suffix_identifier: str, pdf_name: str, parse_dir: str) -> Optional[str]:
|
||||
def get_infer_result(
|
||||
file_suffix_identifier: str, pdf_name: str, parse_dir: str
|
||||
) -> Optional[str]:
|
||||
"""从结果文件中读取推理结果"""
|
||||
result_file_path = os.path.join(parse_dir, f"{pdf_name}{file_suffix_identifier}")
|
||||
if os.path.exists(result_file_path):
|
||||
@@ -107,11 +124,14 @@ def get_infer_result(file_suffix_identifier: str, pdf_name: str, parse_dir: str)
|
||||
|
||||
@app.post(path="/file_parse", dependencies=[Depends(limit_concurrency)])
|
||||
async def parse_pdf(
|
||||
files: List[UploadFile] = File(..., description="Upload pdf or image files for parsing"),
|
||||
output_dir: str = Form("./output", description="Output local directory"),
|
||||
lang_list: List[str] = Form(
|
||||
["ch"],
|
||||
description="""(Adapted only for pipeline and hybrid backend)Input the languages in the pdf to improve OCR accuracy.Options:
|
||||
background_tasks: BackgroundTasks,
|
||||
files: List[UploadFile] = File(
|
||||
..., description="Upload pdf or image files for parsing"
|
||||
),
|
||||
output_dir: str = Form("./output", description="Output local directory"),
|
||||
lang_list: List[str] = Form(
|
||||
["ch"],
|
||||
description="""(Adapted only for pipeline and hybrid backend)Input the languages in the pdf to improve OCR accuracy.Options:
|
||||
- ch: Chinese, English, Chinese Traditional.
|
||||
- ch_lite: Chinese, English, Chinese Traditional, Japanese.
|
||||
- ch_server: Chinese, English, Chinese Traditional, Japanese.
|
||||
@@ -129,41 +149,54 @@ async def parse_pdf(
|
||||
- east_slavic: Russian, Belarusian, Ukrainian, English.
|
||||
- cyrillic: Russian, Belarusian, Ukrainian, Serbian (Cyrillic), Bulgarian, Mongolian, Abkhazian, Adyghe, Kabardian, Avar, Dargin, Ingush, Chechen, Lak, Lezgin, Tabasaran, Kazakh, Kyrgyz, Tajik, Macedonian, Tatar, Chuvash, Bashkir, Malian, Moldovan, Udmurt, Komi, Ossetian, Buryat, Kalmyk, Tuvan, Sakha, Karakalpak, English.
|
||||
- devanagari: Hindi, Marathi, Nepali, Bihari, Maithili, Angika, Bhojpuri, Magahi, Santali, Newari, Konkani, Sanskrit, Haryanvi, English.
|
||||
"""
|
||||
),
|
||||
backend: str = Form(
|
||||
"hybrid-auto-engine",
|
||||
description="""The backend for parsing:
|
||||
""",
|
||||
),
|
||||
backend: str = Form(
|
||||
"hybrid-auto-engine",
|
||||
description="""The backend for parsing:
|
||||
- pipeline: More general, supports multiple languages, hallucination-free.
|
||||
- vlm-auto-engine: High accuracy via local computing power, supports Chinese and English documents only.
|
||||
- vlm-http-client: High accuracy via remote computing power(client suitable for openai-compatible servers), supports Chinese and English documents only.
|
||||
- hybrid-auto-engine: Next-generation high accuracy solution via local computing power, supports multiple languages.
|
||||
- hybrid-http-client: High accuracy via remote computing power but requires a little local computing power(client suitable for openai-compatible servers), supports multiple languages."""
|
||||
),
|
||||
parse_method: str = Form(
|
||||
"auto",
|
||||
description="""(Adapted only for pipeline and hybrid backend)The method for parsing PDF:
|
||||
- hybrid-http-client: High accuracy via remote computing power but requires a little local computing power(client suitable for openai-compatible servers), supports multiple languages.""",
|
||||
),
|
||||
parse_method: str = Form(
|
||||
"auto",
|
||||
description="""(Adapted only for pipeline and hybrid backend)The method for parsing PDF:
|
||||
- auto: Automatically determine the method based on the file type
|
||||
- txt: Use text extraction method
|
||||
- ocr: Use OCR method for image-based PDFs
|
||||
"""
|
||||
),
|
||||
formula_enable: bool = Form(True, description="Enable formula parsing."),
|
||||
table_enable: bool = Form(True, description="Enable table parsing."),
|
||||
server_url: Optional[str] = Form(
|
||||
None,
|
||||
description="(Adapted only for <vlm/hybrid>-http-client backend)openai compatible server url, e.g., http://127.0.0.1:30000"
|
||||
),
|
||||
return_md: bool = Form(True, description="Return markdown content in response"),
|
||||
return_middle_json: bool = Form(False, description="Return middle JSON in response"),
|
||||
return_model_output: bool = Form(False, description="Return model output JSON in response"),
|
||||
return_content_list: bool = Form(False, description="Return content list JSON in response"),
|
||||
return_images: bool = Form(False, description="Return extracted images in response"),
|
||||
response_format_zip: bool = Form(False, description="Return results as a ZIP file instead of JSON"),
|
||||
start_page_id: int = Form(0, description="The starting page for PDF parsing, beginning from 0"),
|
||||
end_page_id: int = Form(99999, description="The ending page for PDF parsing, beginning from 0"),
|
||||
""",
|
||||
),
|
||||
formula_enable: bool = Form(True, description="Enable formula parsing."),
|
||||
table_enable: bool = Form(True, description="Enable table parsing."),
|
||||
server_url: Optional[str] = Form(
|
||||
None,
|
||||
description="(Adapted only for <vlm/hybrid>-http-client backend)openai compatible server url, e.g., http://127.0.0.1:30000",
|
||||
),
|
||||
return_md: bool = Form(True, description="Return markdown content in response"),
|
||||
return_middle_json: bool = Form(
|
||||
False, description="Return middle JSON in response"
|
||||
),
|
||||
return_model_output: bool = Form(
|
||||
False, description="Return model output JSON in response"
|
||||
),
|
||||
return_content_list: bool = Form(
|
||||
False, description="Return content list JSON in response"
|
||||
),
|
||||
return_images: bool = Form(
|
||||
False, description="Return extracted images in response"
|
||||
),
|
||||
response_format_zip: bool = Form(
|
||||
False, description="Return results as a ZIP file instead of JSON"
|
||||
),
|
||||
start_page_id: int = Form(
|
||||
0, description="The starting page for PDF parsing, beginning from 0"
|
||||
),
|
||||
end_page_id: int = Form(
|
||||
99999, description="The ending page for PDF parsing, beginning from 0"
|
||||
),
|
||||
):
|
||||
|
||||
# 获取命令行配置参数
|
||||
config = getattr(app.state, "config", {})
|
||||
|
||||
@@ -171,6 +204,7 @@ async def parse_pdf(
|
||||
# 创建唯一的输出目录
|
||||
unique_dir = os.path.join(output_dir, str(uuid.uuid4()))
|
||||
os.makedirs(unique_dir, exist_ok=True)
|
||||
background_tasks.add_task(cleanup_file, unique_dir)
|
||||
|
||||
# 处理上传的PDF文件
|
||||
pdf_file_names = []
|
||||
@@ -196,20 +230,21 @@ async def parse_pdf(
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"error": f"Failed to load file: {str(e)}"}
|
||||
content={"error": f"Failed to load file: {str(e)}"},
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"error": f"Unsupported file type: {file_suffix}"}
|
||||
content={"error": f"Unsupported file type: {file_suffix}"},
|
||||
)
|
||||
|
||||
|
||||
# 设置语言列表,确保与文件数量一致
|
||||
actual_lang_list = lang_list
|
||||
if len(actual_lang_list) != len(pdf_file_names):
|
||||
# 如果语言列表长度不匹配,使用第一个语言或默认"ch"
|
||||
actual_lang_list = [actual_lang_list[0] if actual_lang_list else "ch"] * len(pdf_file_names)
|
||||
actual_lang_list = [
|
||||
actual_lang_list[0] if actual_lang_list else "ch"
|
||||
] * len(pdf_file_names)
|
||||
|
||||
# 调用异步处理函数
|
||||
await aio_do_parse(
|
||||
@@ -231,13 +266,15 @@ async def parse_pdf(
|
||||
f_dump_content_list=return_content_list,
|
||||
start_page_id=start_page_id,
|
||||
end_page_id=end_page_id,
|
||||
**config
|
||||
**config,
|
||||
)
|
||||
|
||||
# 根据 response_format_zip 决定返回类型
|
||||
if response_format_zip:
|
||||
zip_fd, zip_path = tempfile.mkstemp(suffix=".zip", prefix="mineru_results_")
|
||||
os.close(zip_fd)
|
||||
background_tasks.add_task(cleanup_file, zip_path)
|
||||
|
||||
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
for pdf_name in pdf_file_names:
|
||||
safe_pdf_name = sanitize_filename(pdf_name)
|
||||
@@ -247,7 +284,13 @@ async def parse_pdf(
|
||||
elif backend.startswith("vlm"):
|
||||
parse_dir = os.path.join(unique_dir, pdf_name, "vlm")
|
||||
elif backend.startswith("hybrid"):
|
||||
parse_dir = os.path.join(unique_dir, pdf_name, f"hybrid_{parse_method}")
|
||||
parse_dir = os.path.join(
|
||||
unique_dir, pdf_name, f"hybrid_{parse_method}"
|
||||
)
|
||||
else:
|
||||
# 未知 backend,跳过此文件
|
||||
logger.warning(f"Unknown backend type: {backend}, skipping {pdf_name}")
|
||||
continue
|
||||
|
||||
if not os.path.exists(parse_dir):
|
||||
continue
|
||||
@@ -256,35 +299,63 @@ async def parse_pdf(
|
||||
if return_md:
|
||||
path = os.path.join(parse_dir, f"{pdf_name}.md")
|
||||
if os.path.exists(path):
|
||||
zf.write(path, arcname=os.path.join(safe_pdf_name, f"{safe_pdf_name}.md"))
|
||||
zf.write(
|
||||
path,
|
||||
arcname=os.path.join(
|
||||
safe_pdf_name, f"{safe_pdf_name}.md"
|
||||
),
|
||||
)
|
||||
|
||||
if return_middle_json:
|
||||
path = os.path.join(parse_dir, f"{pdf_name}_middle.json")
|
||||
if os.path.exists(path):
|
||||
zf.write(path, arcname=os.path.join(safe_pdf_name, f"{safe_pdf_name}_middle.json"))
|
||||
zf.write(
|
||||
path,
|
||||
arcname=os.path.join(
|
||||
safe_pdf_name, f"{safe_pdf_name}_middle.json"
|
||||
),
|
||||
)
|
||||
|
||||
if return_model_output:
|
||||
path = os.path.join(parse_dir, f"{pdf_name}_model.json")
|
||||
if os.path.exists(path):
|
||||
zf.write(path, arcname=os.path.join(safe_pdf_name, os.path.basename(path)))
|
||||
zf.write(
|
||||
path,
|
||||
arcname=os.path.join(
|
||||
safe_pdf_name, f"{safe_pdf_name}_model.json"
|
||||
),
|
||||
)
|
||||
|
||||
if return_content_list:
|
||||
path = os.path.join(parse_dir, f"{pdf_name}_content_list.json")
|
||||
if os.path.exists(path):
|
||||
zf.write(path, arcname=os.path.join(safe_pdf_name, f"{safe_pdf_name}_content_list.json"))
|
||||
zf.write(
|
||||
path,
|
||||
arcname=os.path.join(
|
||||
safe_pdf_name, f"{safe_pdf_name}_content_list.json"
|
||||
),
|
||||
)
|
||||
|
||||
# 写入图片
|
||||
if return_images:
|
||||
images_dir = os.path.join(parse_dir, "images")
|
||||
image_paths = glob.glob(os.path.join(glob.escape(images_dir), "*.jpg"))
|
||||
image_paths = glob.glob(
|
||||
os.path.join(glob.escape(images_dir), "*.jpg")
|
||||
)
|
||||
for image_path in image_paths:
|
||||
zf.write(image_path, arcname=os.path.join(safe_pdf_name, "images", os.path.basename(image_path)))
|
||||
zf.write(
|
||||
image_path,
|
||||
arcname=os.path.join(
|
||||
safe_pdf_name,
|
||||
"images",
|
||||
os.path.basename(image_path),
|
||||
),
|
||||
)
|
||||
|
||||
return FileResponse(
|
||||
path=zip_path,
|
||||
media_type="application/zip",
|
||||
filename="results.zip",
|
||||
background=BackgroundTask(cleanup_file, zip_path)
|
||||
)
|
||||
else:
|
||||
# 构建 JSON 结果
|
||||
@@ -298,17 +369,31 @@ async def parse_pdf(
|
||||
elif backend.startswith("vlm"):
|
||||
parse_dir = os.path.join(unique_dir, pdf_name, "vlm")
|
||||
elif backend.startswith("hybrid"):
|
||||
parse_dir = os.path.join(unique_dir, pdf_name, f"hybrid_{parse_method}")
|
||||
parse_dir = os.path.join(
|
||||
unique_dir, pdf_name, f"hybrid_{parse_method}"
|
||||
)
|
||||
else:
|
||||
# 未知 backend,跳过此文件
|
||||
logger.warning(f"Unknown backend type: {backend}, skipping {pdf_name}")
|
||||
continue
|
||||
|
||||
if os.path.exists(parse_dir):
|
||||
if return_md:
|
||||
data["md_content"] = get_infer_result(".md", pdf_name, parse_dir)
|
||||
data["md_content"] = get_infer_result(
|
||||
".md", pdf_name, parse_dir
|
||||
)
|
||||
if return_middle_json:
|
||||
data["middle_json"] = get_infer_result("_middle.json", pdf_name, parse_dir)
|
||||
data["middle_json"] = get_infer_result(
|
||||
"_middle.json", pdf_name, parse_dir
|
||||
)
|
||||
if return_model_output:
|
||||
data["model_output"] = get_infer_result("_model.json", pdf_name, parse_dir)
|
||||
data["model_output"] = get_infer_result(
|
||||
"_model.json", pdf_name, parse_dir
|
||||
)
|
||||
if return_content_list:
|
||||
data["content_list"] = get_infer_result("_content_list.json", pdf_name, parse_dir)
|
||||
data["content_list"] = get_infer_result(
|
||||
"_content_list.json", pdf_name, parse_dir
|
||||
)
|
||||
if return_images:
|
||||
images_dir = os.path.join(parse_dir, "images")
|
||||
safe_pattern = os.path.join(glob.escape(images_dir), "*.jpg")
|
||||
@@ -325,24 +410,24 @@ async def parse_pdf(
|
||||
content={
|
||||
"backend": backend,
|
||||
"version": __version__,
|
||||
"results": result_dict
|
||||
}
|
||||
"results": result_dict,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"error": f"Failed to process file: {str(e)}"}
|
||||
status_code=500, content={"error": f"Failed to process file: {str(e)}"}
|
||||
)
|
||||
|
||||
|
||||
@click.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True))
|
||||
@click.command(
|
||||
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True)
|
||||
)
|
||||
@click.pass_context
|
||||
@click.option('--host', default='127.0.0.1', help='Server host (default: 127.0.0.1)')
|
||||
@click.option('--port', default=8000, type=int, help='Server port (default: 8000)')
|
||||
@click.option('--reload', is_flag=True, help='Enable auto-reload (development mode)')
|
||||
@click.option("--host", default="127.0.0.1", help="Server host (default: 127.0.0.1)")
|
||||
@click.option("--port", default=8000, type=int, help="Server port (default: 8000)")
|
||||
@click.option("--reload", is_flag=True, help="Enable auto-reload (development mode)")
|
||||
def main(ctx, host, port, reload, **kwargs):
|
||||
|
||||
kwargs.update(arg_parse(ctx))
|
||||
|
||||
# 将配置参数存储到应用状态中
|
||||
@@ -359,12 +444,7 @@ def main(ctx, host, port, reload, **kwargs):
|
||||
print(f"Start MinerU FastAPI Service: http://{host}:{port}")
|
||||
print(f"API documentation: http://{host}:{port}/docs")
|
||||
|
||||
uvicorn.run(
|
||||
"mineru.cli.fast_api:app",
|
||||
host=host,
|
||||
port=port,
|
||||
reload=reload
|
||||
)
|
||||
uvicorn.run("mineru.cli.fast_api:app", host=host, port=port, reload=reload)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -56,17 +56,22 @@ def main():
|
||||
model_path = auto_download_and_get_model_root_path("/", "vlm")
|
||||
if (not has_logits_processors_arg) and custom_logits_processors:
|
||||
args.extend(["--logits-processors", "mineru_vl_utils:MinerULogitsProcessor"])
|
||||
"""
|
||||
|
||||
# musa vllm v1 引擎特殊配置
|
||||
device = get_device()
|
||||
if device.startswith("musa"):
|
||||
import torch
|
||||
if torch.musa.is_available():
|
||||
if not has_block_size_arg:
|
||||
args.extend(["--block-size", "32"])
|
||||
if not has_compilation_config:
|
||||
args.extend(["--compilation-config", '{"cudagraph_capture_sizes": [1,2,3,4,5,6,7,8,10,12,14,16,18,20,24,28,30], "simple_cuda_graph": true}'])
|
||||
"""
|
||||
# device = get_device()
|
||||
# if device.startswith("musa"):
|
||||
# import torch
|
||||
# if torch.musa.is_available():
|
||||
# if not has_block_size_arg:
|
||||
# args.extend(["--block-size", "32"])
|
||||
# if not has_compilation_config:
|
||||
# args.extend(["--compilation-config", '{"cudagraph_capture_sizes": [1,2,3,4,5,6,7,8,10,12,14,16,18,20,24,28,30], "simple_cuda_graph": true}'])
|
||||
|
||||
# corex vllm v1 引擎特殊配置
|
||||
device_type = os.getenv("MINERU_LMDEPLOY_DEVICE", "")
|
||||
if device_type.lower() == "corex":
|
||||
if not has_compilation_config:
|
||||
args.extend(["--compilation-config", '{"cudagraph_mode": "FULL_DECODE_ONLY", "level": 0}'])
|
||||
|
||||
# 重构参数,将模型路径作为位置参数
|
||||
sys.argv = [sys.argv[0]] + ["serve", model_path] + args
|
||||
|
||||
@@ -198,6 +198,10 @@ def model_init(model_name: str):
|
||||
if hasattr(torch, 'npu') and torch.npu.is_available():
|
||||
if torch.npu.is_bf16_supported():
|
||||
bf_16_support = True
|
||||
elif device_name.startswith("mlu"):
|
||||
if hasattr(torch, 'mlu') and torch.mlu.is_available():
|
||||
if torch.mlu.is_bf16_supported():
|
||||
bf_16_support = True
|
||||
|
||||
if model_name == 'layoutreader':
|
||||
# 检测modelscope的缓存目录是否存在
|
||||
|
||||
@@ -94,7 +94,11 @@ def get_device():
|
||||
if torch.musa.is_available():
|
||||
return "musa"
|
||||
except Exception as e:
|
||||
pass
|
||||
try:
|
||||
if torch.mlu.is_available():
|
||||
return "mlu"
|
||||
except Exception as e:
|
||||
pass
|
||||
return "cpu"
|
||||
|
||||
|
||||
|
||||
@@ -429,6 +429,9 @@ def clean_memory(device='cuda'):
|
||||
elif str(device).startswith("musa"):
|
||||
if torch.musa.is_available():
|
||||
torch.musa.empty_cache()
|
||||
elif str(device).startswith("mlu"):
|
||||
if torch.mlu.is_available():
|
||||
torch.mlu.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
|
||||
@@ -470,5 +473,8 @@ def get_vram(device) -> int:
|
||||
elif str(device).startswith("musa"):
|
||||
if torch.musa.is_available():
|
||||
total_memory = round(torch.musa.get_device_properties(device).total_memory / (1024 ** 3)) # 转为 GB
|
||||
elif str(device).startswith("mlu"):
|
||||
if torch.mlu.is_available():
|
||||
total_memory = round(torch.mlu.get_device_properties(device).total_memory / (1024 ** 3)) # 转为 GB
|
||||
|
||||
return total_memory
|
||||
|
||||
@@ -109,7 +109,7 @@ repository = "https://github.com/opendatalab/MinerU"
|
||||
issues = "https://github.com/opendatalab/MinerU/issues"
|
||||
|
||||
[project.scripts]
|
||||
mineru = "mineru.cli:client.main"
|
||||
mineru = "mineru.cli.client:main"
|
||||
mineru-vllm-server = "mineru.cli.vlm_server:vllm_server"
|
||||
mineru-lmdeploy-server = "mineru.cli.vlm_server:lmdeploy_server"
|
||||
mineru-openai-server = "mineru.cli.vlm_server:openai_server"
|
||||
|
||||
Reference in New Issue
Block a user