1. はじめに
本記事は、前回の 「非構造データを価値化する!DNPの構造化AI × RAG 体験デモアプリを作ってみた」 の続編です。前回は今後の展望としてローカルLLM実装に触れましたが、今回は実際に gpt-oss:20bを使ったローカル実装 に挑戦した内容を紹介します。
大日本印刷株式会社(DNP)では、企業内の非構造化文書を、生成AIが理解・活用しやすい“AIリーダブルなデータ”に変換する、独自開発のAIソリューション 「DNPドキュメント構造化AI」 を提供しています。
詳細は下記ソリューション/製品・サービスページをご覧ください。
👉 DNPドキュメント構造化AI(AI-Ready Data)
2. ローカルLLMを使う理由
生成AIを業務で活用する際には、セキュリティ・可用性・コストといった観点が重要です。クラウドAPIは便利ですが、以下のような課題が残ります。
-
セキュリティと機密性
社内文書や工場データを外部クラウドに送信すると、情報漏洩のリスクを完全には排除できません。ローカルLLMなら、データを社内に閉じたまま処理できます。 -
オフライン環境での利用
製造現場や研究施設では、インターネット接続に制約があることがあります。ローカル実行なら、ネットワーク環境に依存せず利用可能です。 -
コスト削減と持続可能性
クラウドAPIを利用料に応じた課金が発生します。ローカル運用なら初期投資は必要ですが、長期的にはランニングコストを抑えられます。 -
安定稼働と信頼性
外部サービスの障害に左右されず、社内インフラとして安定的に利用できます。工場ラインや業務システムに組み込む際も、この安定性は大きな価値となります。
これらの理由から、今回はクラウドではなくローカル環境でgpt-oss:20bを稼働させる仕組みを実装しました。
3. 実装したシステム概要
社内GPUマシン上にローカルLLM実行環境を構築し、前回の「構造化データ体験RAGアプリ」で利用するLLMをクラウドAPIからローカルLLMに切り替えました。
以下は、システム構成の概要になります。
-
GPUマシン:NVIDIA GeForce RTX 3090(VRAM 24GB)搭載 Windows PC
-
Webアプリ:Streamlitで構築。Webブラウザから利用可能。exe化して配布も可能
-
LLM実行環境:Ollamaを利用し、API経由でgpt-oss:20bへアクセス
4. ローカルLLM実行環境構築
4.1 Ollamaのインストール
Ollamaは、ローカルで大規模言語モデルを簡単に管理・利用できるツールです。Ollama公式サイトからダウンロードできるWindows版Ollamaのインストーラー(OllamaSetup.exe)で簡単に導入可能です。
4.2 gpt-oss:20bモデルの取得
Ollamaのインストール後、PowerShellやコマンドプロンプトなどのCUIからコマンド一つで取得できます。
ollama pull gpt-oss:20b
また、GUIからでも簡単にダウンロードして利用することができます。
4.3 コンテキストサイズ設定
Ollamaで取得したgpt-oss:20bモデルのコンテキストサイズは初期値が4kですが、Settings画面から最大128kまで変更できます。コンテキストサイズが小さいと、長文のドキュメントを扱う際に情報が途切れてしまう可能性があるため、今回は最大の128kを選択しました。
なお、Context lengthの値を変更するとVRAM使用量が変化します。以下は参考値です。
- 4kに設定時:VRAM 12.6GB使用
- 128kに設定時:VRAM 15.5GB使用
今回はGPUに NVIDIA GeForce RTX 3090(24GB VRAM)を搭載したPCを利用しましたが、16GB以上のVRAM搭載PCが推奨です。
5. Webアプリの改修
5.1 OpenAI互換API利用の場合
OllamaはOpenAI APIと互換性を持つため、WebアプリでOpenAI Python API libraryを利用していた場合、既存コードの改修は最小限で済みます。具体的には、APIエンドポイントをローカルのOllamaサーバーに変更し、モデル名にgpt-oss:20bを指定します。以下はその例です。
pip install openai
import openai
client = openai.OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # ダミーでもOK
)
res = client.chat.completions.create(
model="gpt-oss:20b",
messages=[{"role":"user","content":"こんにちは"}]
)
print(res.choices[0].message.content)
5.2 Ollama Python Library利用の場合
Ollama専用のライブラリ(Ollama Python Library)を利用する方法もあります。ストリーミング応答やパラメータ設定を簡単に扱えるため、今回はこちらを採用しました。以下はOllama Python Libraryを使った例です。
pip install ollama
from ollama import Client
client = Client(host="http://localhost:11434")
res = client.chat(
model="gpt-oss:20b",
messages=[{"role":"user","content":"こんにちは"}]
)
print(res["message"]["content"])
5.3 streamlitアプリの実行ファイル(exe)化
現場で利用する場合、PythonやWebアプリの実行環境を構築するのが難しいケースも想定されます。そんなときに、アプリを実行ファイル(exe)として配布できると、利用者はexeファイルを実行するだけでアプリを使えるため、現場への導入が容易になります。
Pythonアプリをスタンドアローンで動作する実行ファイルに変換する方法としてPyInstallerが一般的ですが、今回はstreamlit-desktop-appを利用しました。streamlit-desktop-appはStreamlitアプリのexe化に特化しており、簡単に実行ファイルを作成できます。
参考リンク:
※実行ファイルに利用するパッケージによってはライセンスに注意する必要があります。業務利用の際には必ず確認してください。
以下は、Ollama Python Libraryを利用してOllamaのgpt-oss:20bモデルを呼び出すStreamlitチャットアプリのサンプルコードです。
import streamlit as st
from ollama import Client
st.title("Ollama Chat (Streamlit版)")
# ユーザー入力
user_input = st.text_input("メッセージを入力してください", "こんにちは")
if st.button("送信"):
try:
client = Client(host="http://localhost:11434")
stream = client.chat(
model="gpt-oss:20b",
messages=[{"role": "user", "content": user_input}],
stream=True
)
output_text = ""
message_area = st.empty()
for chunk in stream:
if "message" in chunk and "content" in chunk["message"]:
output_text += chunk["message"]["content"]
message_area.markdown(output_text)
except Exception as e:
st.error(f"⚠️ エラーが発生しました: {e}")
Windows環境で動作する実行ファイルを作成したい場合は、必ず同じWindows環境でexe化する必要があります。WSL2環境ではなく、直接Windows上で実行してください。
python -m venv .venv
.\.venv\Scripts\activate
pip install streamlit ollama
streamlit run app.py
上記手順によりWindows環境でStreamlitアプリが動作する状態になっていれば、以下のコマンドでWindows実行ファイル(.exe)を作成できます。
pip install streamlit-desktop-app
streamlit-desktop-app build app.py --name "StreamlitOllamaDemo" --pyinstaller-options --onefile --windowed --hidden-import=ollama
streamlit-desktop-appは、PyInstallerを内部で利用してexe化を行うため、PyInstallerのオプションを指定できます。ここでは以下の設定を行いました:
-
--onefile:1つの実行ファイルにまとめる -
--windowed:コンソールウィンドウを表示しない -
--hidden-import=ollama:自動的に依存関係を検出できないモジュールの指定
コマンドの実行後、distフォルダ内にStreamlitOllamaDemo.exeが生成され、実行するとアプリが起動します。
作成したexeファイルを外部PCに配布する場合は、Ollamaがインストールされ、Ollamaサーバーが起動している必要があります。
もし、外部PCからGPUマシンのLLMに直接アクセスさせたい場合は、Clientのhostパラメータをlocalhostから<GPUマシンのIPアドレス>に変更してください。その際、GPUマシン側で11434ポートの開放やファイアウォール設定など行う必要がありますのでご注意ください。
client = Client(host="http://localhost:11434") // ローカル環境からアクセスする場合
client = Client(host="http://<GPUマシンのIPアドレス>:11434") // 外部PCからアクセスする場合
6. 検証結果
6.1 ローカルLLMの速度計測
Ollama Python SDKでは、最後のチャンクに統計情報が含まれる仕様になっています。これを利用し、ストリーミング応答の速度を計測しました。以下のコードでは、チャンクごとに受信したテキストをリアルタイムで表示し、最後に処理時間やトークン数などの統計を出力できます。
import streamlit as st
from ollama import Client
st.title("Ollama Chat (Streamlit版)")
# ユーザー入力
user_input = st.text_input("メッセージを入力してください", "自己紹介してください")
if st.button("送信"):
try:
client = Client(host="http://localhost:11434")
stream = client.chat(
model="gpt-oss:20b",
messages=[{"role": "user", "content": user_input}],
stream=True
)
output_text = ""
message_area = st.empty()
stats_area = st.empty()
for chunk in stream:
if "message" in chunk and "content" in chunk["message"]:
output_text += chunk["message"]["content"]
message_area.markdown(output_text)
# 最後のチャンクに統計が入っている
if chunk.get("done", False):
total_time = chunk.get("total_duration", 0) / 1e9 # 秒換算
prompt_tokens = chunk.get("prompt_eval_count", 0)
prompt_time = chunk.get("prompt_eval_duration", 0) / 1e9
output_tokens = chunk.get("eval_count", 0)
output_time = chunk.get("eval_duration", 0) / 1e9
stats = []
stats.append(f"**Total time**: {total_time:.2f} s")
stats.append(f"**Prompt tokens**: {prompt_tokens}")
stats.append(f"**Prompt eval time**: {prompt_time:.2f} s")
# tokens/s の計算
if prompt_time > 0:
stats.append(f"**Prompt eval rate**: {prompt_tokens / prompt_time:.2f} tokens/s")
stats.append(f"**Output tokens**: {output_tokens}")
stats.append(f"**Output eval time**: {output_time:.2f} s")
# tokens/s の計算
if output_time > 0:
stats.append(f"**Output eval rate**: {output_tokens / output_time:.2f} tokens/s")
stats_area.markdown("### Stats\n" + " \n".join(stats))
except Exception as e:
st.error(f"⚠️ エラーが発生しました: {e}")
上記コードを実行した結果は以下の通りです。
一般的な人間の読書速度は、LLMのトークン生成速度(tokens/s)と比較するとおよそ10 tokens/s程度とされています。チャット用途では「応答に不快感を与えない下限」とされる10 tokens/sを大きく上回り、114 tokens/sで応答できていることを確認しました。これにより、実務で十分に利用可能な性能を備えていると判断できます。
6.2 構造化データ体験RAGアプリの動作確認
次に、構造化データ体験RAGアプリの動作を検証しました。前回と同様に、DNP業績・会社情報(2024年3月期)を対象に同じ質問を行いました。
質問:
「2023年度3月の売上高と売上高純利益率を、前年度比と併せて表示してください。」
回答内容に多少の差異は見られましたが、前回利用したAzure OpenAI Servicesのgpt-4oとほぼ同等に、今回のgpt-oss:20bでもMarkdown形式で保存された表から該当データを抽出し、比較計算と再整形を行ったうえで、分かりやすい形式で結果を表示できることを確認できました。
7. 所感と今後の展望
今回、ローカルLLM(gpt-oss:20b)を用いて「構造化データ体験RAGアプリ」を実装・検証した結果、クラウドAPI(gpt-4o)とほぼ同等のレベルで動作することを確認できました。
応答速度はマシンスペックに依存しますが、gpt-oss:20bを稼働可能な高性能ミニPCも市販されており、より省スペースな環境でローカルLLMを実用化できる可能性があります。特に、ディスプレイ背面に設置できるVESAマウント対応ミニPCを用いれば、工場や製造現場でも場所を取らず、ネットワークに依存しない完全ローカル環境での運用が可能となるでしょう。
さらに今後は、小型かつ高速なSLM(Small Language Model)の登場が予想されます。これらの軽量モデルを活用することで、デバイスの制約がさらに緩和され、現場レベルでのAI活用が一層進むことが期待されます。





