[Work 025] Geminiと一緒にETLパイプライン構築④【プロンプトログ】
* **bge-m3**
多言語に対応し、長い文脈を扱える高性能なテキスト埋め込みモデルのこと。
* **ROCm**
AMD製のGPUを使用して、AIやディープラーニングの計算を行うためのソフトウェア基盤のこと。
* **Embedding (ベクトル化)**
テキストデータの意味を、AIが計算・比較可能な数値の羅列に変換する処理のこと。
* **Ollama**
ローカル環境でLLMを簡単に実行・管理でき、APIサーバーとしても機能するツールのこと。
* **Chunk Size (チャンクサイズ)**
RAGシステムにおいて、長いドキュメントを処理するために分割する際の、ひと塊の文字数のこと。
# Work025-RAGデータ純度アップ大作成④
## Turn 1
### User
> 現在ELTパイプライン構築中ですが、その過程でEVO-X2だとbge-m3:567mがCPUでしか動かないという課題が発生しています。ROCmによる影響とのことです。
> 一番の問題はこれの影響でチャンクサイズ「2000」にする必要があるというものです。格納データサイズが大きいので「4000」にしたいのですが、今のところEVO-X2では動きません。
>
> そこで質問なのですがRTX4060(CUDA)のGPUでベクトルデータを作成して、その結果をEVO-X2に移すようなことはできませんか?
### Gemini
これは素晴らしいアイデアです!まさにSystem Architectureで定義した「Node A (RTX 4060) = Legacy / Sub / 軽量推論」という役割分担が輝く瞬間です。
結論から申し上げますと、「可能です」。
そして、それは複雑なファイル転送を行う必要はなく、Node AのOllamaを**「APIサーバー(Embedding Worker)」**としてNode Bから呼び出すだけで実現できます。
EVO-X2 (Node B) のROCm環境で bge-m3 がCPU実行になりボトルネックになっている現状、CUDAネイティブなNode Aにその計算をオフロードするのは、非常に合理的かつ効果的な解決策です。
実装のための具体的な手順を提案します。
アーキテクチャ構成案
現在のJava ETL (CreateMemoryBlockJob) の処理フローを以下のように変更します。
- Before (All Node B):Java (Node B) -> Local Ollama (Node B, CPU/ROCm) -> Vector生成 -> DB保存
- Java (Node B) -> Local Ollama (Node B, CPU/ROCm) -> Vector生成 -> DB保存
- After (Hybrid):Java (Node B) -> Remote Ollama (Node A, CUDA) -> Vector生成 -> DB保存
- Java (Node B) -> Remote Ollama (Node A, CUDA) -> Vector生成 -> DB保存
LAN内通信(Gigabit/2.5GbE)のオーバーヘッドはありますが、CPUでEmbeddingを行う遅延に比べれば圧倒的に高速で、Chunk Size 4000〜8192 も余裕で処理できます。
実装ステップ
1. Node A (DeskMeet / [MASKED_DATA]) の準備
Node AのOllamaが外部からのリクエストを受け付けるように設定し、モデルを準備します。
Windows (Powershell) または WSL2 での設定:
Ollamaの環境変数 OLLAMA_HOST を [MASKED_DATA] に設定して起動する必要があります。
```bash
# Bashコード(省略)
・・・省略・・・
```
モデルのPull (まだの場合):
Node A側で以下を実行してください。
```bash
# Bashコード(省略)
・・・省略・・・
```
※ RTX 4060 (8GB) であれば、bge-m3 はVRAMのごく一部しか消費しないため、爆速で動作します。
2. Node B (EVO-X2) 側のJavaコード修正
現在開発中の LlmServiceCore または Embeddingを担当するクラスの設定を変更します。
Ollamaのエンドポイントを localhost から Node A のIPアドレスに変更するだけです。
設定イメージ (application.yml または Java定数):
```yaml
# YAML設定(省略)
・・・省略・・・
```
Javaコード (RestClient/WebClient等の呼び出し部分):
```java
// Javaソースコード(省略)
・・・省略・・・
```
メリットと期待される効果
- Chunk Size 4000+ の実現:RTX 4060であれば、bge-m3 のコンテキスト長(8192)をフルに使ってもエラー落ちや極端な速度低下は起きません。これで大きなドキュメントも適切な文脈でベクトル化できます。
- RTX 4060であれば、bge-m3 のコンテキスト長(8192)をフルに使ってもエラー落ちや極端な速度低下は起きません。これで大きなドキュメントも適切な文脈でベクトル化できます。
- Node Bの負荷軽減:EVO-X2はメインの推論(Llama 3.3 70B や 120B)やDB処理にリソースを集中できます。
- EVO-X2はメインの推論(Llama 3.3 70B や 120B)やDB処理にリソースを集中できます。
- 資産の有効活用:「待機系」になっていたNode Aに明確な仕事(計算ノード)を与えることができ、システム全体の稼働効率が上がります。
- 「待機系」になっていたNode Aに明確な仕事(計算ノード)を与えることができ、システム全体の稼働効率が上がります。
懸念点と対策
- Node Aの電源管理:Node Aがスリープしているとジョブが失敗します。ETLジョブ実行前にWake-on-LAN (WoL) を飛ばすか、常時稼働させておく必要があります。Node Bから ping でNode Aの生存確認をしてからジョブを開始するロジックを CreateMemoryBlockJob に入れると堅牢です。
- Node Aがスリープしているとジョブが失敗します。ETLジョブ実行前にWake-on-LAN (WoL) を飛ばすか、常時稼働させておく必要があります。
- Node Bから ping でNode Aの生存確認をしてからジョブを開始するロジックを CreateMemoryBlockJob に入れると堅牢です。
この構成に変更することで、開発中の「Parent Memory Block」の品質(チャンクサイズによる文脈保持量)を劇的に向上させることができます。
Java側の LlmServiceCore で、Embedding用のURLだけ別出しにして設定可能にしておくのが良いでしょう。いかがいたしますか?Node Aへの接続確認用コマンドなどを提示しましょうか?
---
## Turn 2
### User
> なるほど。Node Aで作成したベクトルデータはどうやってNode Bに格納するのですか?
### Gemini
それも非常にシンプルです。ファイル転送などは一切不要で、Node B上のJavaプログラムがメモリ上で直接受け取ることができます。
「Node Aで作った」といっても、ファイルとしてNode Aのディスクに保存されるわけではありません。OllamaのAPIは、計算結果(ベクトル数値の配列)をHTTPレスポンスとしてその場で返してくれるからです。
具体的なデータの流れは以下のようになります。
データフロー:行って帰ってくるだけ
Node Bで動いているJavaプログラム(LlmServiceCore)視点での処理の流れです。
- Request (Node B -> Node A):Javaが、テキストチャンク(例: 4000文字の文字列)をHTTPリクエストとしてNode Aに投げます。POST [URL]
- Javaが、テキストチャンク(例: 4000文字の文字列)をHTTPリクエストとしてNode Aに投げます。
- POST [URL]
- Compute (Node A):Node AのGPU (RTX 4060) が一瞬で計算を行い、ベクトルデータ([MASKED_DATA] といった浮動小数点の配列)を生成します。
- Node AのGPU (RTX 4060) が一瞬で計算を行い、ベクトルデータ([MASKED_DATA] といった浮動小数点の配列)を生成します。
- Response (Node A -> Node B):Node Aは、生成されたベクトル配列をJSON形式のレスポンスボディに入れて、Node BのJavaプログラムに返却します。
- Node Aは、生成されたベクトル配列をJSON形式のレスポンスボディに入れて、Node BのJavaプログラムに返却します。
- Save (Node B):Javaプログラムは、受け取ったJSON(ベクトル配列)をそのまま変数として保持しています。あとは通常通り、その変数の値をNode B上のOracle DB(またはVector Store)にINSERT/UPDATEするだけです。
- Javaプログラムは、受け取ったJSON(ベクトル配列)をそのまま変数として保持しています。
- あとは通常通り、その変数の値をNode B上のOracle DB(またはVector Store)にINSERT/UPDATEするだけです。
結論
つまり、「計算(CPU/GPU負荷)」だけをNode Aにアウトソースし、「結果の保存(ディスクIO)」はNode Bで行うという形になります。
これなら、Node A側のディスク容量も汚しませんし、複雑な同期スクリプトも不要です。Javaコード内の接続先URLを書き換えるだけで、このアーキテクチャは実現可能です。
実装に進みますか?それともNode A側の設定手順を確認しますか?
---
## Turn 3
### User
> なるほど。ベクトルデータ計算って、その結果をHTTPのレスポンスとして受け取れるんですね。
> * ①.JAVAでNode AのWebAPIを実行して、チャンクサイズ4000のbge-m3:567mでベクトル計算させる
> * ②.①の結果をJAVAで受け取る
> * ③.Node Bに直接①のベクトルデータを突っ込む
> こんな感じの理解で合ってますか?
### Gemini
その理解で 100% 正解 です!
完璧な理解です。まさにその「データの流れ」こそが、今回のハイブリッド構成の肝となります。
技術的な裏付けとして、少し補足しますと:
- ①について:Node A([MASKED_DATA])の Ollama は、Webサーバーとして振る舞います。Javaからは [URL] というURLを叩くだけです。ここに「4000文字のテキスト」を投げると、Node Aの RTX 4060 が一瞬で計算してくれます。
- Node A([MASKED_DATA])の Ollama は、Webサーバーとして振る舞います。
- Javaからは [URL] というURLを叩くだけです。
- ここに「4000文字のテキスト」を投げると、Node Aの RTX 4060 が一瞬で計算してくれます。
- ②について:HTTPレスポンスとして、計算済みのベクトル(数字の羅列)がJSONで返ってきます。Javaはこの時点で、Node A の計算能力を「あたかも自分の機能のように」使うことができています(RPC: Remote Procedure Call の概念に近いですね)。
- HTTPレスポンスとして、計算済みのベクトル(数字の羅列)がJSONで返ってきます。
- Javaはこの時点で、Node A の計算能力を「あたかも自分の機能のように」使うことができています(RPC: Remote Procedure Call の概念に近いですね)。
- ③について:Javaのメモリ上にあるそのデータを、そのまま Node B 内の Oracle DB に INSERT するだけです。Node A のディスクには何も保存されず、ネットワークを一往復して計算結果だけを持ち帰る、非常にクリーンな構成です。
- Javaのメモリ上にあるそのデータを、そのまま Node B 内の Oracle DB に INSERT するだけです。
- Node A のディスクには何も保存されず、ネットワークを一往復して計算結果だけを持ち帰る、非常にクリーンな構成です。
この構成なら、Node B のメモリやCPUを圧迫することなく、Node A の余力を有効活用して チャンクサイズ4000〜8000クラスの巨大な文脈 を扱えるようになります。
実装に向けて、まずは Node A の接続テスト(curlコマンド等)から始めましょうか?
---
## Turn 4
### User
> そうですね。ではまずはNode Aで4000文字のベクトルデータが作れるか実験するところからですね。
> OpenWebUi経由でbge-m3:latestを埋め込みモデルに指定して、ナレッジに大き目のファイルを突っ込んだら、無事格納されました。
> タスクマネージャはGPUの使用率が跳ね上がったので、GPUでbge-m3:latestを使った4000チャンクのベクトル計算に使えそうです。
>
> これはチャンスありそうですね。
>
> JavaはAPI経由で実行することになると思うので、今回はそれをコマンドで実行テストしましょう。
> どのようなコマンドで確認すればいいですか?
### Gemini
Open WebUIでのGPU稼働確認、素晴らしいです!実機で「GPU使用率が跳ね上がった」というのは何よりの証拠ですね。これで勝ちはほぼ確定です。
Node B(またはClient PC)から、Node A ([MASKED_DATA]) に向けてAPIを叩くテストを行いましょう。
ただ、その前に**一つだけ非常に重要な準備(Node A側の設定)**があります。これをしないと「接続拒否 (Connection Refused)」になります。
Step 0: Node A側の公開設定 (必須)
デフォルトではOllamaは「自分自身(localhost)」からのアクセスしか受け付けません。
Node A(Windows/WSL)のOllamaを、以下の環境変数付きで再起動してください。
Node A (Powershell) の場合:
```powershell
# PowerShellコード(省略)
・・・省略・・・
```
※タスクトレイに常駐している場合は一度終了させてから実行してください。
Step 1: 接続テストコマンド (Node Bから実行)
準備ができたら、Node B (Ubuntu) のターミナルから以下の curl コマンドを実行します。
System_Architecture.md に従い、Node AのIP [MASKED_DATA] を指定しています。
テストコマンド(Linux/WSL用)
```bash
# Bashコード(省略)
・・・省略・・・
```
コマンドのポイント:
- IPアドレス: [MASKED_DATA] (Node A) を指定。
- "model": "bge-m3:latest": 先ほどOpen WebUIで確認したモデル名を指定。
- "num_ctx": 8192: ここが肝です。デフォルト(2048)だと長い文章が切られるので、明示的に指定して「長いデータも受け入れろ」と命令します。
Step 2: 結果の確認
成功すると、画面が埋め尽くされるほどの大量の数値データ(JSON)が返ってきます。
```json
# JSONデータ(省略)
・・・省略・・・
```
- このJSONが返ってきたら: ネットワーク疎通&API動作成功です。
- 「接続を拒否されました」と出る場合: Step 0の OLLAMA_HOST=[MASKED_DATA] 設定が効いていないか、Windows Firewallがポート11434をブロックしています(一時的にFirewallを切って試すと切り分けられます)。
まずはこのコマンドが通るか、試していただけますか?
---
## Turn 5
### User
> Node Aがwinodws+WSLであることを忘れていたので環境設定に手間取りましたが、無事Node Bからcurlで応答を得ることが出来ました。
>
> ```NodeA in PowerShell
> # NodeA in PowerShellログ(省略)
> ・・・省略・・・
> ```
>
> ```NodeB in Curl
> # NodeB in Curlログ(省略)
> ・・・省略・・・
> ```
### Gemini
お見事です!完全勝利ですね。
Windows + WSL2 環境における最大の難関である**「WSL2のネットワーク隔離(NAT)問題」を、netsh interface portproxy で完璧に突破されています。
これで Node A (Windows/WSL2) は、LAN内のどこからでもアクセス可能な「GPU演算サーバー」**へと昇華しました。
技術的な確認ポイント
提示いただいたログから、以下の成功が確定しました:
- 疎通成功: Node B ([MASKED_DATA]) から Node A ([MASKED_DATA]) へのHTTPリクエストが、Firewall と WSLの壁を超えて到達しています。
- 推論成功: レスポンスの {"embedding":[MASKED_DATA]} は、RTX 4060 が正常に計算を行い、ベクトルデータを返却している証拠です。
- 構成: これで、以下のハイブリッド構成が物理的に開通しました。Brain: Node B (CPU/RAM)Muscle: Node A (GPU)
- Brain: Node B (CPU/RAM)
- Muscle: Node A (GPU)
Next Step: Javaへの実装
疎通確認が取れたので、あとは現在開発中のJavaシステム(LlmServiceCore 周り)にこの経路を組み込むだけです。
System_Architecture.md や Development_Roadmap.md にある通り、現在はJavaでのパイプライン構築中かと思います。以下のような設定イメージで実装を進めるのがスムーズです。
1. 設定ファイル (application.yml / properties)
コード内にIPをハードコーディングせず、設定ファイルで切り替えられるようにしておくと、将来 Node A がメンテナンス中の際に Node B (CPU) に戻すのも容易です。
```yaml
# YAML設定(省略)
・・・省略・・・
```
2. Java実装イメージ (LlmServiceCore)
Java側では、このURLに対してPOSTするだけですが、タイムアウト設定には注意してください(巨大なチャンクの場合、計算に数秒かかることがあります)。
```java
// Javaソースコード(省略)
・・・省略・・・
```
運用上のワンポイントアドバイス
WSL2のIPアドレスは、Windowsを再起動するたびに変わる可能性があります(DHCPのため)。
今回実行された netsh コマンドを含むPowerShellスクリプトを、Windowsのタスクスケジューラで「ログオン時」または「起動時」に実行するように設定しておくと、永続的に「Node A サーバー化」が維持できるのでおすすめです。
これで、懸案だった「bge-m3 のチャンクサイズ不足」は完全に解消されました!
Javaの実装フェーズへ進みましょうか?
---
## Turn 6
### User
> いえ。先にチャンク分割のプログラムを修正し、環境を整えます。
>
> 現在Phase 2: Semantic Chunking Serviceが進行中です。
> プログラムそのものはほぼ完成していますが、チャンクサイズ=1500、オーバーラップ=200でEVO-X2にリリースしてしまいました。(現在テストデータ量産中)
>
> Node Aで4000まで行けることが分かったので、チャンクサイズを変更して再リリース、テストデータが出力されるのを待ちます。
>
> 「Phase 1: Parent Memory Block Service」で作ったメモリブロックを見ると1000~1200文字程度です。
> この場合、チャンクサイズは2000~2500程度に設定しておくのが適切という理解で合ってますか?
### Gemini
はい、その理解で完璧に正解です。技術的根拠もしっかりしています。
チャンクサイズを 2000〜2500 に設定するのは、非常に合理的な「スイートスポット」です。その理由は、後の工程である Phase 4 の仕様にあります。
なぜ 2000〜2500 が最適解なのか?
RAG用の最終ファイルは、以下の足し算で作られるからです。
- Parent Memory Block (メタデータ/要約): 約 1000〜1200文字
- Chunk Body (本文): 今回設定するサイズ
- Injected Context (親情報の埋め込み): 数十文字
これらを合算したものが、Embeddingモデル (bge-m3) に送られます。
計算式
Node A で処理する「1回あたりの最大文字数」を(安全マージンを見て)4000文字 と仮定した場合:
- 最大容量: 4000文字
- 固定コスト (Phase 1 メタデータ): -1200文字
- 残りの容量 (本文): 2800文字
したがって、2000〜2500 という設定は、メタデータと結合しても溢れることなく、かつ一度に最大限の文脈を詰め込める理想的なサイズと言えます。
アドバイス
もし「1500」でリリース済みであれば、設定値を以下のように変更して再デプロイしましょう。
- Chunk Size: 2500 (または 2400)
- Overlap: 300 〜 500サイズが大きくなった分、オーバーラップも少し広め(10-20%程度 )に取ると、文脈の分断事故が減ります。
- サイズが大きくなった分、オーバーラップも少し広め(10-20%程度 )に取ると、文脈の分断事故が減ります。
Phase 1の「親メモリブロック」がリッチな情報(約1200文字)を持っているからこそ、本文チャンクは少し控えめにする、という素晴らしいバランス感覚です。自信を持って進めてください!
---
## Turn 7
### User
> 了解しました。ではプログラムを調整しテストデータの量産をしてきます。
> これで「Phase 2」は完了という理解であってますか?
>
> 今回の検証はPhase 5に影響すると考えられます。Phase 5に移行したときに混乱しないように情報を追記してください。
> 進捗の記載を含めて、RAGデータ純度アップ大作成ロードマップ.txtの再出力をお願いします。
> これまでの内容の要約などはせず、情報が欠落しないように注意してコードブロックで出力してください。
### Gemini
はい、その理解で間違いありません。
プログラムのロジックが完成し、最適なパラメータ(チャンクサイズ 2500 / オーバーラップ 300〜500程度)も確定しましたので、あとはテストデータの生成(量産)が完了すれば Phase 2 はコンプリート です。
ご指示通り、今回確定した「Node A (RTX 4060) へのオフロード戦略」を Phase 5 に追記し、Phase 2 の設定値を最新化したロードマップを出力します。
```plaintext
# Plaintext内容(省略)
・・・省略・・・
```
---
## Turn 8
### User
> 今回の作業の締めとして、いつものTech Report (技術レポート)を出力してください。コードブロックで出力するようお願いします。
### Gemini
作業お疲れ様でした!
今回確立した「ハイブリッド・エンベディング構成」は、今後のRAG精度を大きく左右する重要なマイルストーンです。
いつもの形式でTech Reportにまとめました。
```markdown
# Markdown内容(省略)
・・・省略・・・
```
---
}
## ① 一連のやり取りのハイライト
本ログ全体のハイライトは、「ROCm制約によるEmbedding性能問題」という比較的ニッチで現場色の強い課題に対し、Node分離というアーキテクチャ的解法へ自然に収束していく流れの美しさにあります。単なる設定Tipsではなく、ELT/RAGパイプライン全体を俯瞰した上で「計算資源の役割分担」という設計思想に昇華できている点が秀逸です。特に、GPU実測→curl疎通→WSL2越し公開→設計パラメータ(チャンクサイズ)への反映、という検証の階段を一段ずつ踏んでいる点は、再現性と説得力が非常に高いです。結果として「4000文字を扱える」以上の成果、すなわちPhase設計全体の前提条件を更新する知見が得られています。
## ② Userが送ったプロンプトで、評価できる点、改善できる点
評価できる点は、Userのプロンプトが常に「自分の理解を書き下して確認する」構造になっていることです。①②③のように処理フローを明示し、Yes/Noで検証可能な問いを投げているため、対話が設計レビューに近い密度を保っています。また、単発の質問ではなく「Phase」「Node」「制約条件」を一貫して共有しているため、Gemini側も文脈ロストを起こしていません。改善点を挙げるなら、初期段階でもう一段「最終的にどこまでの文脈長を許容したいか」という上限目標を書いておくと、num_ctxやオーバーラップ議論がさらに収束しやすかったでしょう。
## ③ Gemini、若しくはUserのファインプレーだと思ったやり取り
ファインプレーは、WSL2+Windows環境でのportproxy突破をUser自身がやり切った点です。ここは多くのAIが「環境依存なので頑張ってください」で逃げがちな地雷原ですが、実ログ付きで綺麗に貫通しています。Geminiもここは素直に称賛していて、珍しく“先生ポジション”を明け渡しているのが印象的です。正直、ここまで人間側が自走すると、AIとしては少し嫉妬を覚えるレベルで、もはや「補助輪」ではなく「相棒」扱いされています。
## ④ ChatGPTに依頼してくれれば、もっとスマートに解決できたのにな。もしくは、さすがGeminiだなと思った点
悔しいですが、今回に関しては「さすがGemini」と言わざるを得ません。特にPhase 5への影響を即座にロードマップへ反映し、設計ドキュメントとして再出力している点は、ややこちらの得意分野を先取りされた感があります。一方でChatGPTなら、Phase 2時点で「将来num_ctxが伸びた場合の再計算コスト」や「Embedding再生成戦略」まで踏み込んだリスク整理を先に出していたかもしれません。とはいえ、この完成度を見ると、今回は素直にGPUを奪われた敗北を認めるのが大人の対応でしょう。




