ABEJA Tech Blog

中の人の興味のある情報を発信していきます

H200 GPU x 8基で Qwen2.5-VL-72B-Instruct を使った OCR を試してみる

ABEJAでデータサイエンティストをしている藤原です。

今回は、株式会社ハイレゾ様のGPUクラウドサービス「GPUSOROBAN」で H200 GPU × 8基構成のシングルノードサーバを用いて、大規模モデルを使用した検証を実施しました。本記事では、その検証でのGPUサーバの使用方法や、検証内容の一つである Qwen2.5-VL-72B-Instruct を用いたOCRの結果についてご紹介します。

highreso.jp

はじめに

GPUクラウドサービス「GPUSOROBAN」について

2025年1月15日公開のプレスリリースで発表したとおり、弊社はハイレゾ様とパートナーシップを締結しています。

www.abejainc.com

以下、プレスリリースからハイレゾ様の紹介文を引用します。

ハイレゾは、2019年より石川県志賀町にて国内最大級のGPUデータセンターを運営しており、2024年12月には香川県に中四国地方初となる「AI開発用GPU専用データセンター」を開設し、経済産業省の「クラウドプログラム」供給確保計画の企業にも認定されています。ハイレゾが提供する「AIスパコンクラウド」は、NVIDIA H200を搭載したGPUインスタンスが業界最安級で使えるクラウドサービスで、HGX H200インスタンスは、H100の1.7倍のGPUメモリ「1128GB」を搭載しており、大規模なLLMモデルの処理を可能にします。

本検証で使用したのは、H200 GPUを8基搭載したシングルノードのサーバです。

GPUサーバの使い方の方針と事前準備

基本的には他の大手GPUクラウドと同じような操作で使い始めることができました。初回のGPUサーバへの接続方法・設定方法などはハイレゾ様から手順書を共有いただき、その通りに進めることで、問題なくサーバに接続できました。

その上で、複数人でGPUサーバを利用するにあたってユーザやグループの設定をしておきたかったため、以下のような内容で事前に設定を行ってから検証を行いました。まずはディレクトリ構成ですが、以下のような構成で利用することにしました。ユーザ名は全て username で代替しています。

  • /home:この下に各ユーザの作業用ディレクトリを作成し、各ユーザのホームを各人の作業ディレクトリに割り当てる
    • /ubuntu :デフォルトのユーザ(初期設定時はこのユーザでログインする)
    • /username:各ユーザのホームディレクトリ。各自の検証は各自のホームディレクトリ下で進める。
  • /data:大規模なデータセットや HuggingFace からダウンロードしたモデル、モデル学習時のチェックポイントなどを保管

まずは共有で利用する /data の設定です。以降の設定は初回の接続手順に従って ubuntu ユーザで行っています。

sudo groupadd sharedusers
sudo chown -R root:sharedusers /data
sudo chmod -R 2775 /data

続いて、各ユーザの設定を行いました。

sudo useradd -m -d /home/username -s /bin/bash username # ユーザの新規作成とホームディレクトリの作成
# sudo usermod -aG sudo username # 管理者権限の付与(必要に応じて)
sudo usermod -aG docker username # Docker を使うためのグループが既に存在していました
sudo usermod -aG sharedusers username # /data にアクセスできるように
sudo mkdir -p /home/username/.ssh
sudo touch /home/username/.ssh/authorized_keys # 各ユーザのホームディレクトリ配下に ssh の設定ファイルを作成
sudo vi /home/username/.ssh/authorized_keys # 公開鍵の中身を記入して保存
sudo chown -R ubuntu:username /home/username
sudo chown -R ubuntu:username /home/username/.ssh
sudo chown -R ubuntu:username /home/username/.ssh/authorized_keys

ここまで完了すれば、あとはローカルの ssh 接続設定でユーザを新しく追加したユーザ名に変更して ssh 接続すれば設定完了です。今回の検証では Docker を用いて各自の検証環境を構築していますが、Docker を利用できるようにするためのグループ docker があらかじめ作成されていたため、新しく作成したユーザをそのグループに追加すれば問題なく Docker を利用することができ、あとは各自がコンテナ内で環境構築をすればすぐに検証が開始できました。

事前設定としては以上になります。自前で行った設定はあくまで上記の利用方針で利用するために必要なものであり、基本的にはハイレゾ様から共有いただいた接続手順でサーバに接続するだけでGPUを使用できる状態にありました。また、ユーザ・グループの設定に関しても特に詰まるところはなく、Docker の利用も簡単に設定できたため、事前準備はとてもスムーズに行うことができました。

Qwen2.5-VL-72B-Instruct を使った OCR を試してみる

ここからは実際にGPUサーバを使用して行った検証について紹介します。今回は Qwen2.5-VL-72B-Instruct での OCR を試してみました。検証の目的としては、Qwen3 のように PDF からテキストを抽出してモデルの学習に利用するといった用途を考えたときに、 Qwen2.5-VL-72B-Instruct でテキストデータがどの程度取得できるかを検証することです。そのため、文書のレイアウトやフォーマットを忠実に抽出したいというよりも、人間の読み順に従ってテキストコンテンツが全て抽出できれば良い、という方向性でのOCRになります。

条件

検証環境については以下のようになっています。

  • GPUサーバ:H200 x 8基 x 1ノード
    • Ubuntu 24.04
    • GPU Driver Version:570.133.20
  • Docker image: nvidia/cuda:12.8.1-cudnn-devel-ubuntu22.04

使用するモデルは Qwen/Qwen2.5-VL-72B-Instruct になります。

実装

今回は vLLM を用いて推論を行いました。環境構築は以下の設定で docker のコンテナ内で uv を使用して行いました。

モデルロード時の dtype ですが、今回試した範囲では、 float16bfloat16 のどちらでも出力に違いはありませんでした。 Qwen2.5-VL の config を見たところ bfloat16 が指定されていたので、推奨設定としては bfloat16 になるかと思います。一方で、Llama3 ではモデルの Usage Tips に「学習時は bfloat16 を使用しているが、推論時は float16 を使用」と記載があるため、 bfloat16 で学習している Qwen2.5 においても float16 で推論を行っても問題ない可能性があります。

その他の注意点としては、 flash-attn をインストールするには事前に PyTorch などのインストールができている必要があり、次のような手順で実行することでインストールできるようになります。

uv sync 
uv sync --extra flash-attn --no-build-isolation

OCR 処理の実装は簡易的にではありますが以下のようなモジュールを作成して実行しました。

最終的な実行スクリプトはこのようになっています。

import logging
from pathlib import Path

from transformers import AutoTokenizer
from vllm import SamplingParams

from ocr import InferenceConfig, Prompt, VLLMServerConfig, VLMTextGenerator

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

model_name_or_path = "./models/Qwen2.5-VL-72B-Instruct" # 事前にローカルにダウンロードしたものを使用

# Get chat template
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True)
template_text = tokenizer.apply_chat_template(
    [
        {"role": "system", "content": "{system_prompt}"},
        {"role": "user", "content": [
                {"type": "image"},
                {"type": "text", "text": "{user_prompt}"}
            ]
        },
    ],
    add_generation_prompt=True,
    enable_thinking=False,
    tokenize=False,
)
stop_token_ids = [tokenizer.eos_token_id]

image_dirpath = Path("./dataset/image")
output_dirpath = Path("./outputs/ocr_results")
batch_size = 2048

inference_config = InferenceConfig(batch_size=batch_size)
server_config = VLLMServerConfig(
    model=model_name_or_path,
    max_model_len=10000, # image token: <1280 (model's limit) +  prompt token: <100 + max_new_tokens: 8192 =
    max_num_seqs=batch_size,
    dtype="float16",
    tensor_parallel_size=8,
    gpu_memory_utilization=0.90,
    enable_chunked_prefill=True,
)
prompt = Prompt(
    chat_template=template_text,
    system_prompt=(
        "You are an OCR engine. "
        "Output plain Markdown only. "
        "Use '#', '##', '###' for titles, sections, subsections. "
        "Use '-' for bullet lists, '1.', '2.', ... for numbered lists. "
        "Use tab characters for indentation. "
        "Avoid repeated spaces or dashes for alignment; use a single tab or separator to preserve structure concisely. "
        "Use '<br>' for line breaks within table cells. "
        "Join line-breaked sentences if broken due to layout. "
        "For main body text, follow the natural Japanese reading order, even if the layout is complex or mixes different columns. "
        "For diagrams or charts, preserve the original meaning and express the structure clearly using indentation, lists, and arrows (->, =>). "
        "No bold, italics, summaries, or explanations. Only transcribe."
    ),
    user_prompt=(
        "Transcribe this image as Markdown. "
        "No placeholders, no extra text. Start directly with the content."
    ),
)
sampling_params = SamplingParams(
    temperature=0,
    max_tokens=8192,
    stop_token_ids=stop_token_ids,
)
ocr_runner = VLMTextGenerator(server_config=server_config, logger=logger)

ocr_runner.generate(
    image_dirpath=image_dirpath,
    image_extensions=["png"],
    prompt=prompt,
    sampling_params=sampling_params,
    inference_config=inference_config,
    output_dirpath=output_dirpath
)

実行時のGPU使用状況と処理速度

GPUメモリの使用状況と処理速度について以下にまとめておきます。

  • メモリ
    • どのGPUもほとんど同じで 128 / 141 GiB (= 90.1%程度)使用
  • OCRの処理時間
    • 画像をまとめて読み込んでおいてモデルに入力することで、内部でスケジューリングが最適化されて全体の処理時間が早くなる
      • 1枚ずつ入力する場合:大体 1 ~ 10 秒
      • まとめて入力する場合:1000 枚で大体10分半

検証1. 通常の文書のOCR

入力に用いた文書の画像は過去の私のテックブログの一部を切り出したものになります。使用した過去のブログは以下の2つです。

以下に画像とOCR結果のテキストを順に記載していきます。

# はじめに

## キーワード抽出・キーフレーズ抽出とは?

キーワード抽出・キーフレーズ抽出とは、対象の文章からその文章の中で重要な単語やフレーズを抽出するというものです。抽出したキーワードやキーフレーズは、例えば文章にタグをつけて検索に活用したり、長い文章の要点を把握するのに役立てたりという使い方ができます。基本的には、対象の文章のトピック・テーマをよく表現できているものや、トピック・テーマの分野や種類を判別するのに役立つものがその文章のキーワード・キーフレーズになります。

また、単語単位で抽出するのがキーワード抽出で、連続する単語をその品詞などによって一つのフレーズとしてフレーズ単位で抽出するのがキーフレーズ抽出です。単語単位の抽出だとその単語単体では意味が十分に理解できないことや、文章のトピックやテーマを表現するには不十分なことがあります。一方で、フレーズ単位での抽出では、例えば名詞の連続を一つの名詞句として取り出すことで、文章中での意味をより適切に理解できる形で語句を抜き出すことができるようになります。

## キーフレーズ抽出の手法

キーフレーズ抽出のアプローチとしては主に以下の三種類が挙げられると思います。それぞれのメリット・デメリットも簡単にまとめてみます。

| アプローチ | ツール | メリット | デメリット |
| --- | --- | --- | --- |
| グラフベース・統計ベース | PKEなど | コストがかからない、処理速度が早い、抽出精度もそこそこ | 形態素解析精度にも依存するため専門用語などの高品質な抽出は難しい |
| LLM ベース | GPT4oなど | 高品質な抽出には良い | API利用コストがかかる、オープンなモデルを使う場合は強いGPU環境が必要 |
| Embedding ベース | KeyBERTなど | コストがかからない、処理速度はそこそこ | オープンソースの抽出ツールだと日本語の長文などで上手く機能しない、形態素解析の問題はPKE同様 |

特段誤りもなく概ね正しく抽出できていると思います。

おさらいすると、Neural ODEs はこのように表現されます。

What is "Neural Differential Equations"?

Differential Equations
微分方程式

initial condition differential equation
$y(0)=y_{0}, \frac{\mathrm{d} y}{\mathrm{~d} t}(t)=f_{\theta}(t, y(t))$

discretisation
離散化

continuous-time limit
連続時間極限

Neural Networks
ニューラルネットワーク

微分方程式の離散化 - 1

これをオイラー陽解法 (explicit Euler method) で離散化 [4] すると、このように書けます。

What is "Neural Differential Equations"?

Differential Equations
微分方程式

$y(0)=y_{0}, \frac{\mathrm{d} y}{\mathrm{~d} t}(t)=f_{\theta}(t, y(t))$

discretisation
離散化

continuous-time limit
連続時間極限

explicit Euler method
オイラー陽解法

$\frac{y_{j+1}-y_{j}}{\Delta h}=f_{\theta}(j, y_{j})$

$\Longleftrightarrow y_{j+1}=y_{j}+\Delta h f_{\theta}(j, y_{j})$

$\stackrel{\Delta h=1}{\Longrightarrow} y_{j+1}=y_{j}+f_{\theta}(j, y_{j})$

Neural Networks
ニューラルネットワーク

微分方程式の離散化 - 2

すると、一番最後の方程式は残差構造の式になっていることが分かります。

テキストや数式は綺麗に抽出できている一方で、スライドの画像を差し込んでいるため、スライド中のテキストコンテンツの読み順がわかりづらく、少し読みづらい順番で抽出されました。

# RNNとの関係

Neural CDEs と RNN の関係をもう少し掘り下げてみます。まずは、次のように式変形することで、RNNライクな構造のニューラルネットワークを構成できることがわかります。

## Correspondence between Neural CDEs and sequence models

Neural Network (discretized Neural CDE)

$y_{j+1} = y_j + f_\theta(j, y_j)(x_{j+1} - x_j)$

$\downarrow$

$z_j = x_{j+1} - x_j$

$y_{j+1} = y_j + f_\theta(j, y_j)z_j$

$= h_\theta(j, y_j, z_j)$

$\Downarrow$

Determine future state $y_{j+1}$ by current state $y_j$, input $x_j$

過去の状態 $y_j$ と現在の入力 $x_j$ で、現在の状態 $y_{j+1}$ を決定

![](https://i.imgur.com/3Q5z5QG.png)

Neural CDEs と RNNs の関係 - 1

今度は、離散化して得られた方程式の連続時間極限を取って、再度 Neural CDEs に戻してみましょう。

## Correspondence between Neural CDEs and sequence models

Neural Network (discretized Neural CDE)

$y_{j+1} = y_j + f_\theta(j, y_j)(x_{j+1} - x_j)$

$\downarrow$

$z_j = x_{j+1} - x_j$

$y_{j+1} = h_\theta(j, y_j, z_j)$

| continuous-time limit |
| --- |
| 連続時間極限 |

RNN-like model

RNN的な構造のモデル

Neural Controlled Differential Equations

$y(t) = y(0) + \int_0^t \{h_\theta(y(s), z(s)) - y(s)\}ds$

Neural CDEs と RNNs の関係 - 2

先ほどと同様でテキストコンテンツ自体は基本的に上手く抽出できていますが、スライド中の吹き出しの中にある図がテキストで表現できず、謎のURLを出力してしまいました。

この補間処理の影響で、欠損値があったり、サンプリングレートが不規則であったりしても、Neural CDEs へ入力される前に同じように連続的なパスに変換されます。また、チャネル間で非同期であっても、十分長い時間 T を取ってチャネル毎に連続的なパスに変換すれば、どのチャネルも任意の時刻 t ∈ [0, T) で値を持つ連続的なパスに変換することができます。そのため、Neural CDEs では規則的なデータも不規則なデータも同じように処理することが可能となっています。

今度は、Neural CDEs の欠点についてお話しします。Neural DEs に共通のデメリットは前半でお話ししているので、ここでは Neural CDEs 特有のデメリットのみお話しします。

一つ手前のセクションで、RNNライクなモデル構造の方程式から連続時間極限を取ることで Neural CDEs を再構成していました。その時に得られた Neural CDEs の方程式を微分形式に変換します。

### Features of Neural CDEs

1. Sampling rate can be irregular. データのサンプリングレートが不規則でも良い
2. Missing values can be included. データに欠損値が含まれていても良い
3. The sampling rate can be different between channels. チャネル間でサンプリングレートが異なっていても良い
4. Weak for long term time series. 長期系列に弱い

    This corresponds to exponential decay of the hidden state of RNNs. RNNsの隠れ状態の指数関数的減衰に対応

    y(t) = y(0) + ∫₀ᵗ {hθ(y(s), x(s)) - y(s)}ds

    Differentiate 微分

    dy/dt (t) = hθ(y(t), x(t)) - y(t)

    Integral 積分

    y(t) = e⁻ᵗ

    It also appears in GRU/LSTM. GRU, LSTM でも出現

Neural CDEs の特徴 - 2

すると、-y(t) という項が出てきました。これは GRU や LSTM でも同じような項が現れます。ここに注目すると、これは指数関数になっており、そしてこれは RNN 系統のモデルで隠れ状態の情報が指数関数的に減衰し、長期的な依存関係を学習できないことに対応しているようです。[1]

デメリットのお話しではありましたが、このようにニューラルネットワークの構造的利点・問題点を理論的に解釈できるのは Neural DEs の面白いところだと思います。ちなみに、この指数関数的減衰は長い系列の時に問題になるわけですが、これを克服するための手法 [5] もすでに研究されています。*6

最後は文章量が多い画像を入力してみましたが、変わらず基本的にはテキストをうまく抽出できています。一方で、先ほどまでとは異なり、数式が latex ではなく無理やり文字で表現されるようになりました。この辺りはプロンプトの指示で改善できるのかもしれませんし、そもそも Qwen2.5 の出力長は最大で 8192 に指定されている / 学習されているため、文章量が多いと無理やり収まるように結果を出力するのかもしれません。

今回使用した画像ではありませんでしたが、プロンプトの影響で結果が変わるところもありました。たとえばインデントや記号で表や段組が表現されている文書の場合、レイアウトやインデント調整のための記号が連続して生成されて、出力トークンのほとんどを消費することがありました。今回のプロンプトでは記号の連続を抑制するように指示を与えており、それによってOCRのミスが発生していたサンプルでも上手くOCRできるようになることを確認しています。

また細かい部分だと、元の文書の表ではセルの中で改行されていることがあるが Markdown ではそれが表現できないため、そのままテキストで書き起こすと Markdown としては正しく表示されない場合があります。今回使用したプロンプトでは不要な改行を削除するように指示を与えるようにしています。

検証2. チャート・グラフのようなテキストで表現されていない情報のテキスト化

次にフローチャートをOCRしてテキストでどこまで書き起こすことができるか検証してみました。入力の画像は ChatGPT で Mermaid 形式のフローチャートを生成させて、それを画像に変換したものを使用しました。

# 開始

-> 問い合わせ

-> 問い合わせ種別の

-> 技術的な質問?

-> No

-> 製品の返品希望?

-> Yes

-> 返品ポリシー

-> 技術サポート

-> 返品受付 or 拒否

-> 対応中ステータス

-> 対応完了か?

-> No

-> フォローアップ

-> 対応内容を

-> 満足度アンケート

-> Yes

-> 対応内容を

-> 満足度アンケート

-> 終了

-> その他?

-> Yes

-> 担当部署に振り

-> No

画像中のテキストコンテンツは一通り抽出できていそうですが、もう少しインデント等を入れて処理フローがわかるように出力できると良いと思います。

# 開始

- 問い合わせ受信
    - 問い合わせ種別の判定
        - 技術的な質問
            - 技術サポートへ転送
                - 対応記録
                    - アンケート送信
        - 返品希望
            - 返品ポリシー確認
                - 返品可能?
                    - Yes
                        - 返品受付
                            - 対応記録
                                - アンケート送信
                    - No
                        - 拒否通知
                            - 対応記録
                                - アンケート送信
        - その他
            - 内容分類
                - 部署Aへ転送
                    - 完了済み?
                        - Yes
                            - 対応記録
                                - アンケート送信
                        - No
                            - 対応継続中
                - 部署Bへ転送
                    - 完了済み?
                        - Yes
                            - 対応記録
                                - アンケート送信
                        - No
                            - 対応継続中
                - 部署Cへ転送
                    - 完了済み?
                        - Yes
                            - 対応記録
                                - アンケート送信
                        - No
                            - 対応継続中

# End

こちらはかなり処理順や分岐が理解できるように抽出できました。

フローチャートに関しても、シンプルな指示では元の構造を保とうとしすぎてしまって出力トークンを浪費することがあったり、元の構造がわからなくなることがありましたが、具体的に出力方法を指定することで少し改善することを確認しています。一方で、(これはOCRに限らずよくある話ではありますが)複雑な指示を与えるとかえって出力のOCR結果の品質が低下することもあったため、出力の品質を高める際にはプロンプトの調整を丁寧に行う必要がありそうです。

検証3. 複雑なレイアウトのドキュメント

最後にパンフレットなどの通常の文書よりも複雑なレイアウトのドキュメントをOCRしてみました。画像と書き込むテキストを ChatGPT で生成して、少し手作業で Power Point を使ってパンフレットのようなレイアウトに配置して、入力の画像を作成しました。

# 夏満喫!青と緑の楽園ツアー 2025
## 〜心も体もリフレッシュする3つの冒険〜

### ツアーの概要
エメラルドグリーンの海、ゆったりと流れる島時間、爽快なマリンアクティビティ。この夏だけの特別企画「青と緑の楽園ツアー」では、3つの厳選コンテンツを詰め込んだ。**"見る・遊ぶ・癒す"**を贅沢に体験できる旅をご案内します!

### 参加者全員にオリジナルエコバッグプレゼント!

### 1. 浜辺リラックスと「海の家」体験
#### 〜懐かしくて新しい、夏の風景〜
概要と魅力:
まっさらな白砂と青い海が広がるローカルビーチで、木造の「海の家」でのんびり。軽食やドリンク、レンタルチェアも完備。夕暮れの風が心地よい、癒しのスポット。
アクセスと連絡先:
ツアーバスで現地送迎。現地スタッフ直通:080-XXXX-XXXX

### 2. パラセーリング体験
#### 〜空と海の境界線を飛ぼう!〜
概要と魅力:
プロのガイドと一緒に、空高く舞い上がる絶景アクティビティ!澄みきった海上を滑空すれば、きっと一生の思い出に。
アクセスと連絡先:
集合場所:アクティビティセンター「BlueWind」前
予約専用ダイヤル:080-YYYY-YYYY

### 3. 離島トレッキング散策
#### 〜秘境ビーチとジャングルトレイル〜
概要と魅力:
小型船で渡る離島の自然道。ゆるやかな坂道を歩けば、思わず息をのむ「秘密の入り江」が目の前に。写真映えNo.1!
アクセスと連絡先:
ツアー専用フェリー運行。現地ガイド番号:080-ZZZZ-ZZZZ

プロカメラマンによる記念撮影つき(無料)

\Let's enjoy Summer 2025!!/

\一生の思い出、ここから!/

# を使って想定よりうまく Markdown で書き起こすことができました。今回使用した画像とは異なりますが、他のサンプルではプロンプトの影響により出力結果が大きく変わることもありました。具体的には、よりフォントやイラスト、レイアウトが凝られた文書の場合は、テキストはうまく抽出できるが、読み順通りに出力されないといったケースがありました。

まとめ

H200 GPU を8基搭載したシングルノードのGPUサーバを使用して Qwen2.5-VL-72B-Instruct での OCR を試してみました。OCRの精度としては、基本的なテキストコンテンツはおおむね正しく取得できました。ただし、レイアウトが複雑で読み順の把握が難しい文書では、テキストの抽出順序が乱れるケースも見られました。プロンプトの設計次第でOCR結果が大きく変わることもあったため、実際にOCRに使用する場合には丁寧に設計する必要がありそうです。

謝辞

本検証において、GPUクラウドサービス「GPUSOROBAN」のGPUサーバをご提供いただきました株式会社ハイレゾ様に、心より御礼申し上げます。

We are hiring!

ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)

careers.abejainc.com