2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OCI A10 GPUにMinerUをデプロイして文書解析APIを立てる

2
Last updated at Posted at 2026-07-19

はじめに

RAG を業務で利用する場合、回答生成モデルだけでなく、PDF や画像から検索可能なテキストを作る前処理が重要になります。

社内ドライブには、次のような形式の異なる資料が混在しています。

  • テキスト主体の PDF
  • 表を含む会議資料
  • スキャンされた帳票
  • 複数カラムのマニュアル
  • 図やチャートを含む提案書
  • PowerPoint から出力した PDF
  • Office 文書

通常の PDF テキスト抽出では、見出し、読み順、表の行列関係が崩れることがあります。

そこで今回は、文書解析ツールの MinerU 3.4 を OCI の NVIDIA A10 GPU インスタンスへデプロイし、PDF / 画像 / Office ファイルを直接解析できる mineru-api として利用します。

使用する Terraform スタックは次のリポジトリです。

https://github.com/engchina/no.1-oci-a10-mineru-deployer

この記事では、2026-08-09 に公開した v0.2.12 を前提に説明します。

https://github.com/engchina/no.1-oci-a10-mineru-deployer/releases/tag/v0.2.12

まず結論

現在のスタックは、古い vLLM OpenAI-compatible API だけの構成ではありません。

OCI Resource Manager から実行すると、次の環境を自動構築します。

OCI Resource Manager
  |
  v
OCI Compute
VM.GPU.A10.1 / VM.GPU.A10.2
Ubuntu 24.04
  |
  +-- NVIDIA Driver
  +-- Docker
  +-- NVIDIA Container Toolkit
  |
  v
Docker image build
vllm/vllm-openai:a230095847e93bd4df9888b33dab956fa9504537b828a23657d2b26fed57b5c9
  + mineru[core]==3.4.4
  + onnxruntime-gpu==1.28.0
  |
  v
mineru-api
MinerU 3.4 hybrid-engine

デプロイ後は、次の API で PDF を直接アップロードできます。

POST http://<PUBLIC_IP>:80/file_parse
POST http://<PUBLIC_IP>:80/tasks
GET  http://<PUBLIC_IP>:80/tasks/<task_id>
GET  http://<PUBLIC_IP>:80/tasks/<task_id>/result
GET  http://<PUBLIC_IP>:80/health

重要なのは、この記事の構成では /v1/chat/completions へ画像を投げる vLLM 単体 API ではなく、MinerU 公式の mineru-api を起動する点です。

そのため、PDF をページ画像へ分解して OpenAI 互換 API に投げる必要はありません。

旧構成との違い

以前の構成は、opendatalab/MinerU2.5-Pro-2605-1.2B を vLLM OpenAI-compatible API として公開するものでした。

その場合の構成は、概念的には次のようになります。

PDF
  |
  v
クライアント側でページ画像へ変換
  |
  v
/v1/chat/completions
  |
  v
MinerU VLM

この方式でも GPU は使えますが、完全な MinerU の PDF / Office 解析パイプラインではありません。

現在の構成では、mineru-api が次の処理をサーバー側で担当します。

PDF / 画像 / Office
  |
  v
mineru-api
  |
  +-- レイアウト解析
  +-- VLM 解析
  +-- 表解析
  +-- Markdown 生成
  +-- 中間 JSON 生成
  +-- 画像抽出

API の呼び出し方も変わります。

旧: /v1/models
旧: /v1/chat/completions

新: /file_parse
新: /tasks
新: /health

MinerU とは

MinerU は、PDF や文書画像から、本文、見出し、表、画像などの文書構造を抽出するためのドキュメント解析ツールです。

単に画像内の文字を左上から順番に読む OCR ではなく、ページ内のレイアウトを解析して、後段で利用しやすい構造へ変換することを得意とします。

代表的な出力は次のとおりです。

Markdown
HTML Table
Content List
Middle JSON
Model Output
Extracted Images

このため、次のような処理に向いています。

PDF の Markdown 化
RAG 用テキストの生成
見出し単位のチャンク分割
表構造の保持
文書内画像の分離

RAG 前処理で MinerU が効く理由

RAG では、PDF から文字が取れればよいわけではありません。

次の情報をできるだけ残す必要があります。

  • 見出しと本文の関係
  • 表の行列関係
  • ページ番号
  • 画像の位置
  • 文書内の章構造
  • 元ファイルとの対応

MinerU の Markdown と HTML Table を使うと、これらを後段の処理へ渡しやすくなります。

MinerU
  |
  +-- Markdown
  |      -> 見出し単位で分割
  |
  +-- HTML Table
  |      -> 表単位で分割
  |
  +-- Images
  |      -> 必要な画像だけ別 OCR
  |
  +-- Metadata
         -> 元 PDF・ページ番号と関連付け

実運用では、まず MinerU で文書全体を処理し、MinerU でテキスト化できなかった図やチャートだけを別の視覚 OCR へ渡す構成が扱いやすいです。

PDF
  |
  v
MinerU で一次解析
  |
  +-- 本文・見出し
  |      -> Markdown を利用
  |
  +-- 表
  |      -> HTML Table を利用
  |
  +-- 図・チャート
         -> 必要なものだけ別 OCR

対応構成

v0.2.12 の主な仕様は次のとおりです。

項目 内容
リージョン 東京 ap-tokyo-1 / 大阪 ap-osaka-1
OS Canonical Ubuntu 24.04
GPU VM.GPU.A10.1 / VM.GPU.A10.2
API mineru-api FastAPI
公開ポート TCP 80
MinerU mineru[core]==3.4.4
実測バージョン MinerU 3.4.4
backend hybrid-engine
effort high を推奨
ベースイメージ vllm/vllm-openai:a230095847e93bd4df9888b33dab956fa9504537b828a23657d2b26fed57b5c9
Python MinerU 公式対応範囲の >=3.10,<3.14 を Docker build 時に検証
ONNX Runtime CPU 版を削除し、onnxruntime-gpu==1.28.0 を導入
GPU メモリー使用率 A10.1 / A10.2 とも 0.5
VLM プリロード 有効
A10.1 runtime 既定値 同時解析数 2、処理ウィンドウ 64
A10.2 runtime 既定値 同時解析数 4、処理ウィンドウ 64
ネットワークルール スタックでは作成しない

A10.1 と A10.2 の違い

シェイプ GPU 構成 Tensor Parallel 既定同時解析数 既定処理ウィンドウ
VM.GPU.A10.1 A10 24GB x 1 1 2 64
VM.GPU.A10.2 A10 24GB x 2 2 4 64

A10.2 は、48GB の単一 GPU として動作するわけではありません。

24GB の A10 を 2 枚使い、--tensor-parallel-size 2 として vLLM 側のモデル処理を分散します。

GPU 0 --+
        +-- MinerU VLM
GPU 1 --+

検証や少人数の利用では、まず A10.1 で始めるのが扱いやすいです。

同時処理数を増やしたい場合や、実 PDF の長時間負荷テストで A10.1 の余裕が足りない場合は、A10.2 を検討します。

デプロイ前の準備

デプロイ前に、次の OCI リソースを用意します。

  • コンパートメント
  • A10 GPU のサービス制限
  • A10 GPU の空き容量がある可用性ドメイン
  • VCN
  • パブリック・サブネット
  • インターネット・ゲートウェイ
  • 外部 package / model download 用の egress
  • SSH 公開鍵
  • API 接続元を制限する Security List または NSG

v0.2.12 のスタックは、NSG、Security List、ホスト firewall を作成しません。

これは意図した設計です。ネットワーク、セキュリティ、リソース配置は、運用側で手動管理する前提にしています。

本番用途では、少なくとも次を手動で制御してください。

TCP 22
  -> 管理端末の CIDR のみ

TCP 80
  -> 利用元サーバー、API Gateway、Load Balancer など必要な CIDR のみ

Egress
  -> package download / model download に必要な宛先

OCI Resource Manager からデプロイする

1. Deploy to Oracle Cloud を開く

GitHub リポジトリの README にある Deploy to Oracle Cloud ボタンを押します。

v0.2.12 の Resource Manager 用 ZIP は次の URL です。

https://github.com/engchina/no.1-oci-a10-mineru-deployer/releases/download/v0.2.12/v0.2.12.zip

2. OCI 環境を選択する

Resource Manager の入力画面で、次の項目を指定します。

コンパートメント
可用性ドメイン
VCN
パブリック・サブネット
SSH 公開鍵

東京リージョンは次のとおりです。

ap-tokyo-1

大阪リージョンは次のとおりです。

ap-osaka-1

現在のスタックでは、OCI provider の region は選択した subnet OCID から推定します。

そのため、Resource Manager のフォーム上で以前のような region 入力が見えなくても問題ありません。

ただし、次の 3 つは同じリージョンにそろえる必要があります。

availability_domain
vcn_id
subnet_id

3. Ubuntu image の扱い

instance_image_ocid を空欄にすると、選択した subnet のリージョンとシェイプで利用可能な最新の Canonical Ubuntu 24.04 platform image を Terraform plan 時に選択します。

東京と大阪では image OCID が異なります。

このため、Osaka を選択した場合は Osaka 側の image が lookup されます。

将来の Ubuntu image 更新による差し替わりを避けたい場合は、Resource Manager の output selected_ubuntu_image_ocid を控えて、次回以降 instance_image_ocid に指定します。

4. GPU シェイプを選択する

検証環境では、まず次の構成から始めます。

Shape:
VM.GPU.A10.1

Boot Volume:
200 GB

API Port:
80

同時処理数を増やす場合は、VM.GPU.A10.2 を選択します。

Resource Manager のフォームでは、選択した shape に応じて A10.1 用または A10.2 用の推奨値だけを表示します。

5. MinerU と model source を設定する

既定の MinerU package は次のとおりです。

mineru[core]==3.4.4

完全な再現性を優先する場合は、次のように固定できます。

mineru[core]==3.4.4

モデル取得元は次から選択できます。

huggingface
modelscope

Hugging Face からの取得が遅い環境では、mineru_model_download_sourcemodelscope に変更できます。

Hugging Face token は任意です。

hf_xxxxxxxxxxxxxxxxx

API 認証について

現在の mineru-api 自体には、sk- で始まる API key 認証はありません。

旧 vLLM OpenAI-compatible API の場合は、次のような Authorization header を使いました。

Authorization: Bearer sk-example-mineru-api-key

しかし、この記事の mineru-api 構成では、この方式は使いません。

本番公開する場合は、ネットワーク側と前段コンポーネントで保護します。

推奨例は次のとおりです。

Client
  |
  v
OCI API Gateway / Load Balancer / Reverse Proxy
  |
  +-- TLS
  +-- 認証
  +-- CIDR 制限
  |
  v
mineru-api

少なくとも、Security List または NSG で API 接続元 CIDR を絞ってください。

初期化の流れ

Apply 後、インスタンス上では mineru-bootstrap.service が初期化を実行します。

処理の流れは次のとおりです。

A10 インスタンス作成
  |
  v
Ubuntu 24.04 起動
  |
  v
NVIDIA Driver 確認
  |
  +-- 未導入ならインストール
  |        |
  |        v
  |      自動再起動
  |
  v
Docker 導入
  |
  v
NVIDIA Container Toolkit 導入
  |
  v
MinerU GPU image build
  |
  v
MinerU model download
  |
  v
mineru-api 起動
  |
  v
GPU / CUDA / ONNX Runtime / hybrid-engine 検証

NVIDIA driver が導入されていない場合、初期化中にインスタンスが 1 回再起動します。

再起動後は、systemd service が初期化処理を継続します。

初期化ログを確認する

Apply 完了後も、Docker image build や model download が続いている場合があります。

SSH 接続後、次のコマンドで初期化 service を確認します。

sudo systemctl status mineru-bootstrap.service --no-pager

ログをリアルタイムで確認します。

sudo journalctl -u mineru-bootstrap.service -f

同じ内容は次のファイルにも出力されます。

/var/log/mineru-bootstrap.log

コンテナログは次で確認します。

sudo docker logs -f mineru-api

MinerU の永続化先は次のとおりです。

/opt/mineru/home
/opt/mineru/output
/opt/mineru/logs

bootstrap の状態ファイルは次の場所にあります。

/var/lib/mineru-deployer

初期化完了マーカーは次です。

/var/lib/mineru-deployer/bootstrap-complete

GPU を確認する

ホスト上で GPU を確認します。

nvidia-smi

コンテナ内からも確認します。

sudo docker exec -it mineru-api nvidia-smi

PyTorch CUDA と ONNX Runtime CUDA provider を確認します。

sudo docker exec -it mineru-api bash -lc '
python3 - <<PY
import torch
import onnxruntime as ort

print("torch.cuda.is_available =", torch.cuda.is_available())
print("torch.version.cuda =", torch.version.cuda)
print("onnxruntime providers =", ort.get_available_providers())
PY
'

少なくとも次の状態を期待します。

torch.cuda.is_available = True
onnxruntime providers = [..., "CUDAExecutionProvider", ...]

スタックは起動後にこれらを自動検証します。

CPU fallback を GPU 配備成功として扱わないようにしています。

API の動作を確認する

Resource Manager の output には、次が含まれます。

mineru_api_url
mineru_docs_url
mineru_health_url

ヘルスチェックを実行します。

curl "http://<PUBLIC_IP>:80/health"

正常であれば、次のような JSON が返ります。

{
  "status": "healthy",
  "version": "3.4.4",
  "protocol_version": 2,
  "queued_tasks": 0,
  "processing_tasks": 0,
  "completed_tasks": 0,
  "failed_tasks": 0,
  "max_concurrent_requests": 2,
  "processing_window_size": 64
}

PDF を同期解析する

高精度の同期解析例です。

curl -X POST "http://<PUBLIC_IP>:80/file_parse" \
  -F "files=@sample.pdf" \
  -F "backend=hybrid-engine" \
  -F "effort=high" \
  -F "image_analysis=true" \
  -F "formula_enable=true" \
  -F "table_enable=true" \
  -F "return_md=true" \
  -F "return_middle_json=true" \
  -F "return_content_list=true" \
  -F "return_images=true" \
  -F "response_format_zip=true" \
  -o mineru-result.zip

backend=hybrid-engine は明示することを推奨します。

MinerU 3.4 の CLI / API では既定 backend が hybrid-engine ですが、クライアントや将来バージョンの既定値変更による誤解を避けるためです。

速度を優先する場合は、effort=higheffort=medium に変更できます。

PDF を非同期解析する

長時間処理や大きな文書では、非同期 API を使うとクライアント側の HTTP timeout を避けやすくなります。

TASK_ID=$(
  curl -sS -X POST "http://<PUBLIC_IP>:80/tasks" \
    -F "files=@sample.pdf" \
    -F "backend=hybrid-engine" \
    -F "effort=high" \
    -F "image_analysis=true" \
    -F "formula_enable=true" \
    -F "table_enable=true" \
    -F "return_md=true" \
    -F "return_middle_json=true" \
    -F "return_content_list=true" \
    -F "return_images=true" \
    -F "response_format_zip=true" |
  python3 -c 'import json,sys; print(json.load(sys.stdin)["task_id"])'
)

curl "http://<PUBLIC_IP>:80/tasks/${TASK_ID}"
curl -L "http://<PUBLIC_IP>:80/tasks/${TASK_ID}/result" -o mineru-result.zip

GET /tasks/<task_id> は状態確認です。

この polling 自体は GPU inference を発生させません。

ローカルホストで smoke test する

API インスタンス上で簡単な PDF を作ってテストする場合は、次のようにできます。

python3 - <<'PY'
from pathlib import Path

path = Path("/tmp/mineru-smoke-test.pdf")
lines = [
    "MinerU API smoke test PDF",
    "Generated on Linux for local API testing.",
    "This file has simple searchable text.",
]

ops = ["BT", "/F1 18 Tf", "72 720 Td"]
for index, line in enumerate(lines):
    if index == 1:
        ops.append("/F1 12 Tf")
    if index > 0:
        ops.append("0 -28 Td")
    escaped = line.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
    ops.append(f"({escaped}) Tj")
ops.append("ET")
stream = ("\n".join(ops) + "\n").encode("latin-1")

objects = [
    b"1 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n",
    b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
    b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 1 0 R >> >> /Contents 4 0 R >>\nendobj\n",
    f"4 0 obj\n<< /Length {len(stream)} >>\nstream\n".encode("latin-1") + stream + b"endstream\nendobj\n",
    b"5 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n",
]

pdf = bytearray(b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n")
offsets = [0]
for obj in objects:
    offsets.append(len(pdf))
    pdf.extend(obj)
xref_offset = len(pdf)
pdf.extend(f"xref\n0 {len(objects) + 1}\n".encode("latin-1"))
pdf.extend(b"0000000000 65535 f \n")
for offset in offsets[1:]:
    pdf.extend(f"{offset:010d} 00000 n \n".encode("latin-1"))
pdf.extend(
    (
        f"trailer\n<< /Size {len(objects) + 1} /Root 5 0 R >>\n"
        f"startxref\n{xref_offset}\n%%EOF\n"
    ).encode("latin-1")
)

path.write_bytes(pdf)
print(path)
PY

ローカルから同期 API を呼びます。

curl -sS -X POST "http://127.0.0.1:80/file_parse" \
  -H "accept: application/json" \
  -F "files=@/tmp/mineru-smoke-test.pdf;type=application/pdf" \
  -F "backend=hybrid-engine" \
  -F "effort=high" \
  -F "parse_method=auto" \
  -F "image_analysis=true" \
  -F "formula_enable=true" \
  -F "table_enable=true" \
  -F "return_md=true" \
  -F "return_middle_json=false" \
  -F "return_model_output=false" \
  -F "return_content_list=false" \
  -F "return_images=false" \
  -F "response_format_zip=false" \
  -F "return_original_file=false"

成功すると、次のように status=completed と Markdown が返ります。

{
  "status": "completed",
  "backend": "hybrid-engine",
  "version": "3.4.4",
  "results": {
    "mineru-smoke-test": {
      "md_content": "## MinerU API smoke test PDF\n\nGenerated on Linux for local API testing.\n\nThis file has simple searchable text."
    }
  }
}

ランタイム値を負荷テストする

v0.2.12 では、MinerU API インスタンス上で runtime 値を matrix test するスクリプトを追加しています。

scripts/mineru_runtime_stress_test.py

確認対象は主に次の 2 つです。

MINERU_API_MAX_CONCURRENT_REQUESTS
MINERU_PROCESSING_WINDOW_SIZE

実行例です。

sudo python3 scripts/mineru_runtime_stress_test.py \
  --api-url "http://127.0.0.1:80" \
  --concurrency-values 1,2,3 \
  --window-values 32,48,64 \
  --case-duration-seconds 60

実運用に近い判断をする場合は、生成 PDF ではなく実際の文書を入れたディレクトリを指定します。

sudo python3 scripts/mineru_runtime_stress_test.py \
  --api-url "http://127.0.0.1:80" \
  --pdf-dir /path/to/test-pdfs \
  --concurrency-values 1,2,3 \
  --window-values 32,48,64 \
  --case-duration-seconds 180

スクリプトは各組み合わせごとに次を行います。

/etc/mineru-deployer/mineru.env を更新
mineru-api container を再起動
/health を確認
/tasks へ非同期リクエストを投入
GPU memory / GPU util / latency / failure を記録
summary.tsv を出力
最後に元の env へ戻す

既定では、テスト後に元の /etc/mineru-deployer/mineru.env へ戻します。

推奨値をそのまま残す場合だけ、--keep-best を付けます。

sudo python3 scripts/mineru_runtime_stress_test.py \
  --api-url "http://127.0.0.1:80" \
  --pdf-dir /path/to/test-pdfs \
  --concurrency-values 3 \
  --window-values 64 \
  --case-duration-seconds 600 \
  --keep-best

A10.1 の負荷テスト結果

生成 PDF を使った 60 秒 matrix test では、次の結果でした。

1/32: throughput 34.14/min, p95 2.140s, failures 0/35
1/48: throughput 34.29/min, p95 2.190s, failures 0/35
1/64: throughput 34.21/min, p95 2.181s, failures 0/35
2/32: timeout, failures 2/2
2/48: throughput 47.30/min, p95 5.087s, failures 0/48
2/64: throughput 48.23/min, p95 5.180s, failures 0/49
3/32: throughput 61.69/min, p95 3.505s, failures 0/63
3/48: throughput 62.78/min, p95 3.319s, failures 0/64
3/64: throughput 63.31/min, p95 3.217s, failures 0/65

生成 PDF では 3/64 が最速でした。

ただし、生成 PDF は実運用のスキャン PDF、複雑な表、数式、長文ドキュメントより軽いです。

そのため、Terraform の A10.1 既定値は、性能と安定性のバランスを見て次にしています。

MINERU_API_MAX_CONCURRENT_REQUESTS=2
MINERU_PROCESSING_WINDOW_SIZE=64

A10.2 では、2 GPU の余裕を使う性能寄りの既定値として次にしています。

MINERU_API_MAX_CONCURRENT_REQUESTS=4
MINERU_PROCESSING_WINDOW_SIZE=64

本番採用前には、実 PDF で 10 分以上のテストを推奨します。

sudo python3 scripts/mineru_runtime_stress_test.py \
  --api-url "http://127.0.0.1:80" \
  --pdf-dir /path/to/test-pdfs \
  --concurrency-values 2,3 \
  --window-values 64 \
  --case-duration-seconds 600

MINERU_PROCESSING_WINDOW_SIZE が影響するもの

MINERU_API_MAX_CONCURRENT_REQUESTS は、API が同時に処理する解析 task 数です。

MINERU_API_MAX_CONCURRENT_REQUESTS=2
  -> 同時に 2 task まで処理

MINERU_PROCESSING_WINDOW_SIZE は、MinerU が 1 task 内で扱う処理ウィンドウの大きさです。

MINERU_PROCESSING_WINDOW_SIZE=64
  -> 1 task 内の処理単位を 64 にする

大きい window は大文書で throughput が出やすい一方、GPU memory や内部 queue への負荷が増える可能性があります。

小さい window は常に安定するとは限りません。

実測では、2/32 は 2 task とも processing のまま timeout しました。

一方、2/643/64 は成功しました。

つまり、concurrency と window は単独で判断せず、組み合わせで測る必要があります。

GPU 使用率が 0% に見える場合

nvidia-smiGPU-Util が 0% のままでも、必ずしも GPU 未使用という意味ではありません。

例えば、次のような状態があり得ます。

GPU memory:
  python3          1390MiB
  VLLM::EngineCore 10834MiB

GPU-Util:
  0%

この場合、モデルは GPU memory 上にロードされています。

ただし、GET /tasks/<task_id> は状態確認の polling です。

この HTTP access 自体は GPU inference を発生させません。

また、短い PDF や検索可能 PDF では GPU kernel 実行が短く、1 秒間隔の watch nvidia-smi では utilization の山を見逃すことがあります。

GPU 負荷を確認したい場合は、次を使います。

nvidia-smi dmon -s pucm -d 1
sudo docker logs -f mineru-api

実 PDF の処理中も GPU-Util と stress test の max_gpu_util_percent が常に 0 の場合は、次を確認します。

curl -s http://127.0.0.1:80/health | python3 -m json.tool
sudo docker logs --tail=200 mineru-api
sudo docker exec -it mineru-api nvidia-smi

今回遭遇した問題と修正

ここからは、実際の検証中に遭遇した問題と修正内容を整理します。

1. 旧構成が完全な MinerU API ではなかった

最初の構成は、vLLM OpenAI-compatible API として MinerU VLM を公開するだけでした。

この場合、PDF を直接アップロードできず、完全な MinerU pipeline でもありません。

修正後は、mineru-api を起動する構成に変更しました。

旧:
/v1/chat/completions
MinerU2.5-Pro VLM

新:
/file_parse
/tasks
MinerU 3.4 hybrid-engine

2. MinerU の最新系に合わせる必要があった

検証時点では MinerU 3.4.4 を使用しました。

Docker build は、MinerU 公式 Dockerfile の install 経路に合わせています。

python3 -m pip install -U "mineru[core]==3.4.4"
python3 -m pip cache purge

Python は MinerU 公式対応範囲の >=3.10,<3.14 を Docker build 時に検証します。

3. pip cache purge が失敗した

一時期、pip cache を無効化した状態で次を実行していました。

python3 -m pip cache purge

その結果、次のエラーになりました。

ERROR: pip cache commands can not function since cache is disabled.

修正として、Docker build から cache 無効化を外し、公式 Dockerfile と同じように pip install -U 後に pip cache purge を実行する形に戻しました。

4. pip resolver conflict が表示された

vLLM base image には lmcache などが入っており、MinerU 3.4 系の transformers<5 要件と metadata 上の conflict が表示されることがあります。

例です。

lmcache requires huggingface_hub>=1.5.0
lmcache requires transformers>=5.4
mineru installs transformers 4.x

これは公式 Dockerfile と同じ install 経路でも表示され得る metadata conflict です。

このため、pip check で build を落とすのではなく、起動後に実際の GPU / CUDA / ONNX Runtime / hybrid-engine を検証する方針にしました。

5. PaddlePaddle GPU を追加導入しない

検証中、paddlepaddle-gpu を追加導入するかを確認しました。

しかし、MinerU 公式 Dockerfile は追加で paddlepaddle-gpu を入れていません。

追加導入すると dependency resolver conflict や CUDA package index の問題を増やす可能性があります。

そのため、既定では次のように空欄にしています。

paddlepaddle_gpu_package = ""

必要がある場合だけ明示的に指定します。

6. ONNX Runtime GPU を明示導入する

CPU 版 onnxruntime が入っていると、GPU provider が使われない可能性があります。

そのため、Docker build では CPU 版を削除してから GPU 版を入れます。

python3 -m pip uninstall -y onnxruntime || true
python3 -m pip install "onnxruntime-gpu==1.28.0"

起動後には、CUDAExecutionProvider が見えることを検証します。

7. debconf: delaying package configuration は問題ではない

Ubuntu package install 中に次の warning が表示されることがあります。

debconf: delaying package configuration, since apt-utils is not installed

これは minimal image でよく出る warning であり、package install が失敗していなければ修正不要です。

8. Docker build が止まって見えた

Docker build では、base image pull、MinerU install、model download に時間がかかります。

例えば次のような箇所で止まって見えることがあります。

Step 3/17 : ARG MINERU_PACKAGE_SPEC

この場合は、別 terminal から次を確認します。

sudo journalctl -u mineru-bootstrap.service -f
sudo docker ps -a
sudo docker system df

ログが進んでいれば待ちます。

9. CUDA-capable device(s) is/are busy or unavailable

最初の API smoke test で、次の CUDA error が発生しました。

CUDA error: CUDA-capable device(s) is/are busy or unavailable

原因は、vLLM と pipeline 側が同じ GPU を使う hybrid workload で、GPU compute mode が適切でない状態でした。

修正として、起動時に NVIDIA GPU compute mode を Default にそろえる処理を追加しました。

nvidia-smi -c 0

現在の bootstrap は、compute mode を確認し、必要なら Default に変更します。

10. public API から server_url を渡すと SSRF 保護に引っかかった

API 呼び出し時に、次のエラーが出ました。

{
  "detail": "Publicly exposed API disables *-http-client backends and server_url by default. Rebind to 127.0.0.1 or start with --allow-public-http-client if you understand the SSRF risk."
}

原因は、public に bind された API へ server_url=string を渡したことです。

MinerU は SSRF リスクを避けるため、public exposed API では server_url や http-client backend を既定で無効化します。

通常の /file_parse/tasks 呼び出しでは、server_url を送る必要はありません。

修正後の curl では、server_url を削除します。

curl -sS -X POST "http://127.0.0.1:80/file_parse" \
  -F "files=@/tmp/mineru-smoke-test.pdf;type=application/pdf" \
  -F "backend=hybrid-engine" \
  -F "effort=high" \
  -F "image_analysis=true" \
  -F "return_md=true"

--allow-public-http-client は SSRF リスクを理解している場合だけ使います。

通常は使わない方が安全です。

11. NSG / Security List は作成しない方針にした

途中の設計では、Terraform 側で network security group を作る案もありました。

しかし、実運用ではネットワーク、セキュリティ、配置ポリシーを手動管理したいケースが多いため、スタックからは NSG / Security List / host firewall 作成を削除しました。

現在の方針は次です。

Terraform stack:
  -> Compute と MinerU API を作る

Network security:
  -> 既存 VCN / subnet / Security List / NSG を運用側で管理

12. Region 選択と image OCID の扱い

Resource Manager 画面で region 入力が見えないため、最初は不安になりました。

現在の stack では、選択した subnet OCID から region を推定します。

subnet_id
  |
  v
selected_region
  |
  v
OCI provider region
  |
  v
Ubuntu 24.04 image lookup

東京と大阪では image OCID が異なります。

そのため、subnet が Osaka の場合は Osaka 側の image が lookup されます。

13. /tasks polling が続いていても GPU が動いていないように見える

ログに次のような行が大量に出ることがあります。

GET /tasks/<task_id> HTTP/1.1 200 OK

これは task 状態確認の polling です。

この access 自体は GPU を使いません。

GPU が使われているかは、処理中の nvidia-smi dmon、コンテナ内の nvidia-smi、PyTorch CUDA、ONNX Runtime provider で確認します。

14. stress test 中に進行状況が見えにくかった

最初の stress test script は、各 case が終わるまで CASE_RESULT を出しませんでした。

そのため、重い task が走ると止まって見えました。

現在は 15 秒ごとに PROGRESS を出します。

PROGRESS case=concurrency-3_window-64 elapsed_seconds=45 remaining_seconds=15 completed=48 successes=48 failures=0 current_gpu_util_percent=100 peak_gpu_util_percent=100

completed が増えていれば処理は進んでいます。

remaining_seconds が 0 になったあとも戻らない場合は、最後に投入された task の完了または timeout を待っています。

15. processing のまま終わらない task を timeout として扱う

2/32 のテストでは、2 task が processing のまま完了しませんでした。

旧 script では、最後に取得した task status が processing のため、失敗理由が分かりにくい表示でした。

現在は timeout 時に次を残すようにしています。

status=timeout
last_task_status=processing

これにより、API submit は成功したが task が完了しなかったことを区別できます。

セキュリティ上の注意

API をそのまま広く公開しない

mineru-api 自体には認証がありません。

本番で API を広く公開する場合は、次を検討してください。

  • Security List / NSG による接続元制限
  • OCI API Gateway
  • OCI Load Balancer
  • Reverse Proxy
  • TLS
  • アプリケーション側の認証
  • Private subnet
  • VPN / FastConnect

FastAPI Docs を無効化する

既定では FastAPI docs が有効です。

本番で CIDR を広くする場合は、次を検討してください。

enable_fastapi_docs = false

Terraform state と Compute metadata に注意する

hf_token を指定した場合、Terraform state と Compute cloud-init user data に含まれます。

state と Compute metadata の閲覧権限を最小化してください。

RAG 向けの処理フロー

実運用では、MinerU を一次解析器として利用します。

入力ファイル
  |
  v
MinerU
  |
  +-- Markdown
  |      |
  |      v
  |   見出し単位でチャンク分割
  |
  +-- HTML Table
  |      |
  |      v
  |   表単位でインデックス化
  |
  +-- 抽出画像
         |
         v
      必要なものだけ視覚 OCR で補完

MinerU の出力に次のような画像参照が含まれる場合、その画像だけを別 OCR へ送ります。

![](images/example.jpg)

さらに、MinerU の結果が短すぎるページを検出して、fallback する方法もあります。

if len(extracted_text.strip()) < minimum_length:
    run_visual_ocr(page_image)

実際には、文字数だけでなく、次の条件を組み合わせます。

本文が空
本文が極端に短い
画像参照だけが含まれる
表が期待されるのに表がない
ページの大部分が画像

これにより、全ページを複数モデルへ送るよりも、GPU コストを抑えられます。

まとめ

no.1-oci-a10-mineru-deployerv0.2.12 を使うと、MinerU 3.4 の完全な mineru-api を OCI A10 GPU 上に構築できます。

OCI Resource Manager
  |
  v
Ubuntu 24.04 + NVIDIA A10
  |
  v
Docker + NVIDIA Container Toolkit
  |
  v
mineru-api
  |
  v
MinerU 3.4 hybrid-engine

重要なポイントは次です。

PDF を直接 /file_parse または /tasks へアップロードできる
backend=hybrid-engine を明示する
effort=high で高精度解析を使う
GPU / CUDA / ONNX Runtime / hybrid-engine を起動後に検証する
NSG / Security List / firewall は手動管理する
public API に server_url を渡さない
A10.1 既定値は 2/64
A10.2 既定値は 4/64
実 PDF では stress test で最終値を決める

MinerU は、テキスト PDF、会議録、仕様書、マニュアル、表を含む業務文書の一次解析に向いています。

一方で、図やチャート内部の数値、SmartArt 内の文字などは、抽出画像を別 OCR で補完する構成が扱いやすいです。

まず MinerU で文書全体を高速に構造化し、画像として残った部分だけを補完する。

この 2 段構成が、RAG 前処理において文書構造、処理速度、GPU コストのバランスを取りやすい構成です。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?