3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DGX Sparkで探る、検証・改善可能なAI × Harness-4

3
Last updated at Posted at 2026-10-08

【第4部】DGX Sparkを動かして見えてきた、ローカルAI運用の「見えないコスト」

電力・GPU・通信をObservabilityする


はじめに

これまで、DGX Sparkを使ってローカルAIの環境を少しずつ構築してきました。

第1部では、一体のHermes Agentから始め、Qwen系とGemma系の二体のAgentをOmnigent上で討議させる構成まで広げました。第2部では、既存のREST APIで利用していたRikAI2をMCP Toolとして再構成し、Agentから専門的な外部機能を利用できるようにしました。第3部では、Memory、Trace、Policyを使い、Agentを動かすだけでなく、その動作を記録し、後から検証できる仕組みについて考えました。

ここまでの取り組みでは、主にAgentやHarnessの内側を見てきました。どのAgentが、どのToolを呼び、どのような応答を返したのか。その過程をどう残し、どう検証するのか、という視点です。

一方、DGX Sparkを実際に常時稼働させるようになると、それとは別のことが気になり始めました。モデルは動いており、APIも応答を返し、Agentも動作しています。しかし、次のように聞かれると、意外と答えられません。

  • GPUは実際に何W使っているのか
  • モデルを常駐させているだけで、どれだけ電力を消費しているのか
  • ネットワークでは、何がどれだけ流れているのか
  • いまGPUを使っているのは誰なのか
  • モデルをUnloadしてよい状態なのか
  • 何もしていないように見える時間に、本当に何も起きていないのか

AIが動いていることと、AIシステムを運用できていることは、別の話でした。

そこで今回は、DGX Sparkを「AIを動かす箱」ではなく、「観測すべきシステム」として扱うことにしました。作ったのは大げさな監視基盤ではありません。小さなexporterであるhwagentと、Prometheus、Grafana、Grafana Beyla(eBPF)というデファクトスタンダードのツールを組み合わせ、電力、GPU、モデルの常駐状態、ネットワークを同じダッシュボードから確認できるようにしたものです。

なお、今回の仕組みも、これまでと同じくHermes Agentと相談しながら組み立てました。hwagentの実装やUnloadの判定の仕組みなどは、Agentの提案をもとにしています。何を観測したいか、どこまで自動化してよいかを決め、生成されたコードや設定を確認し、実際に動かして採用するかを判断したのは人間の側です。

実際に数日程度の動作を観測してみると、ローカルAIにはクラウドAIとは少し違う「見えないコスト」があることが分かってきました。本稿では、観測の仕組みを作った話に加えて、観測によって何が分かり、そこから次にどのような制御が必要だと考えるようになったのかを整理します。


0. 今回使うツール

本題に入る前に、今回使うツールを簡単に紹介しておきます。

この環境のために作った部品はhwagentだけです(Agentと相談しながら作成)。残りの3つは、監視やObservabilityの分野ですでにデファクトスタンダードになっているツールです。

そのため、読み進めるうえで必要なのは各ツールの細かい使い方ではなく、「どれが何を担当しているのか」という役割分担です。

# ツール 立ち位置 本稿での役割 ポート(任意)
1 hwagent(独自作成) 標準ライブラリとpsutilだけで動く小さなPython製exporter GPU電力、Ollamaの常駐状態など約50項目、TCP接続のpeer / RTT / retransを出力 :9464
2 Prometheus メトリクスを保存する時系列DBのデファクトスタンダード hwagentとBeylaのメトリクスを定期的に取得して保存(本環境では30日保持) :9095
3 Grafana 時系列データを可視化するツールのデファクトスタンダード Prometheusのデータを読み、「HW — GB10」パネル群として表示 :3100
4 Grafana Beyla Grafana Labsが提供する、eBPFを使った観測ツール 箱全体の通信量を request / response / unknown の3方向に分けて出力 :9110

hwagentはホスト上でsystemd user serviceとして動かしています。Prometheus、Grafana、Beylaは、一つのDocker Composeファイル(hwprobe-mcp/grafana/docker-compose.yml)でまとめて起動します。

なお、すでにPrometheusとGrafanaを運用している場合、これらをローカルに新しく立てる必要はありません。既存のPrometheusに「この箱のexporterもscrapeしてほしい」と設定を追加し、既存のGrafanaにパネルを足すだけで済みます。今回ローカルに立てたのは、DGX Spark単体で検証を完結させたかったためです。

図1. DGX Sparkを観測する構成
図1. DGX Sparkを観測する構成


1. 数日動かして気付いた、モデルの常駐と電力の関係

DGX SparkのようなローカルAI環境では、モデルを起動し、そのまま常駐させておくのが最も簡単な運用です。例えばQwen 3.8-27Bをロードしておけば、リクエストが来たときにすぐ推論できます。

ただし、モデルが常に使われているとは限りません。夜間や昼休みなど、しばらく推論リクエストが来ない時間帯もあります。ここで問題になるのは、「モデルをロードしている」ことと「モデルを使っている」ことは同じではない、という点です。
今回のDGX Sparkで観測したところ、Qwen 3.8-27Bを常駐させて実際に推論している場面では、GPU電力が68〜87W程度まで上昇し、別の日にも74〜77W程度の値を観測しました。一方、モデルをUnloadした後には11.7〜11.8Wまで低下しました。

ただし、モデルが常駐している間、ずっとこの高い電力を消費していたわけではありません。 常駐中でも、推論している時間とアイドルの時間では電力が変わります。ここで示した68〜87Wや74〜77Wは、推論時に観測した値であり、モデル常駐中の平均電力ではありません。

また、これらは同じDGX Spark、同じGPU電力メトリクス hw_gpu_power_w による実測値ですが、「モデルがロードされているものの完全にアイドルな状態」と「モデルをUnloadした状態」を、条件をそろえて厳密に比較したものではありません。そのため、推論中とUnload後の電力差を、すべてモデルの常駐によるものと捉えることはできません。

そこで、瞬間的な電力値を見るだけでなく、モデルの常駐状態も記録し、常駐していた時間と未ロードだった時間を分けて平均電力を確認することにしました。この集計方法については、後の章で説明します。

数日動かし続けて分かったのは、電力を考える際には、モデルが載っているかどうかだけでなく、その間に何をしているかも合わせて見る必要がある、ということでした。推論中なのか、常駐したまま待機しているのか、それとも解放されているのか。これらを分けて観測しなければ、どの状態にどれだけの電力が使われているのかを説明できません。

クラウドAPIを利用しているだけでは、モデルの常駐や解放を利用者側で意識する場面はあまりありません。しかしローカルAIでは、推論の性能だけでなく、モデルをいつ載せ、いつ解放するかも運用設計の対象になります。 必要なときにすぐ使える状態と、使わない時間のリソース解放をどう両立するか。その判断のために、まず箱の状態を観測する必要があると考えました。


2. GPUの電力制御は、思ったほど自由ではなかった

それであれば、使っていない時間はGPUのPower Limitを下げればよいのではないか、と最初は考えました。NVIDIA GPUでは、次のようなコマンドで電力やクロックを制御する方法が知られています。

nvidia-smi -pl
nvidia-smi -lgc
nvidia-smi -ac

しかし今回のDGX Spark環境では、これらを想定したようには変更できませんでした。CPU側のGovernorもroot管理になっていました。GPUだからといって、ユーザー空間から自由に電力を制御できるわけではありません。
ここで、システム設計として一つ整理が必要になりました。「できるはずの制御」を探し続けるのではなく、この環境でユーザー権限から実際に制御できるレバーは何なのかを切り分けることです。
整理すると、次のようになりました。

GPU Power Limit
      ↓
  直接制御できない
CPU Governor
      ↓
  root管理
モデル常駐
      ↓
  ユーザー空間から制御できる

今回の環境で現実的だったのは、モデルの常駐と解放でした。そこで、GPUの電力を細かく制御するのではなく、「必要なときだけモデルを載せる」という、より上位のレイヤーで制御する方針に切り替えました。

図2. 直接の電力制御から、モデルの常駐制御へ
図2. 直接の電力制御から、モデルの常駐制御へ


3. hwagent:箱の「体」を数値にする自作exporter

モデルの常駐と解放を制御するには、まず箱の状態を数値として取り出す必要があります。そのために作ったのがhwagentです。本稿で唯一の独自部品です。
hwagentは、Hermes Agentに「DGX Sparkの電力やモデルの常駐状態を、Prometheusで見られるようにしたい」と相談するところから作り始めました。Agentが構成やコードを提案し、それを動かしながら確認と修正を重ねて、今の形になっています。
hwagentの役割は大きく二つあります。
一つ目は、exporterとしての役割です。GPU電力、CPUやGPUの利用率、Ollamaのモデル常駐状態など、約50項目のメトリクスを、Prometheusが読めるテキスト形式で /metrics エンドポイントから公開します。exporterとは、ある対象の状態をPrometheus形式のメトリクスとして公開する小さなプログラムのことです。Linuxサーバー全般にはnode_exporter、NVIDIA GPUにはDCGM Exporterなどの既製品があります。今回はOllamaの常駐状態やUnload判定など、ローカルLLMの運用に固有の情報を同じ場所で扱いたかったため、自作しました。

hwagent metrics --host 0.0.0.0 --port 9464

地味ですが重要だったのが、127.0.0.1 ではなく 0.0.0.0 でListenさせることでした。Docker側で動くPrometheusから、host gateway経由で取得するためです。
なお、0.0.0.0でListenすると、hwagentのメトリクスエンドポイントはホストの全ネットワークインターフェースで待ち受けることになります。今回の構成ではDocker側のPrometheusから取得するためにこの設定としていますが、実運用ではファイアウォールで接続元を制限する、Dockerブリッジ側のアドレスだけにバインドするなど、利用環境に合わせて公開範囲を絞る必要があります。
二つ目は、power CLIとしての役割です。モデルをUnloadしてよいかどうかを判定し、実際に解放するところまでを担当します。


4. 「Unloadしてよいか」をPolicyとして扱う

モデルをUnloadすること自体は難しくありません。難しいのは、いつUnloadしてよいのかを判断することです。
単純に「一定時間推論がなければUnloadする」というルールにすると、次のような状況で問題が起きる可能性があります。

  • Agentがバックグラウンドで動いている
  • 別プロセスが推論を開始しようとしている
  • まだ処理中のリクエストがある
  • 監視上はアイドルに見えるが、実際には利用中である
    そこで今回は、「いつ確認するか」「してよいか」「実際に行う」の三つを、別々の仕組みに分けました。

トリガー     cron(時間で起動)
    ↓
判定と許可   policyノート + decide_unload()
    ↓
実行と検証   hwagent unload() → /api/ps で確認 → ログとVaultに記録

トリガー:cronが1日4回確認する

0  9  * * *  power_check_cron.sh idle   # 09:00
30 12 * * *  power_check_cron.sh idle   # 12:30
0  18 * * *  power_check_cron.sh idle   # 18:00
0  19 * * *  power_check_cron.sh off    # 19:00 業務時間外

9時、12時半、18時は idle モードで、アイドルであれば解放します。19時だけは off モードで、業務時間外として扱います。

判定と許可:policyノートと decide_unload()

実際に解放してよいかは、hwagentの power.py にある decide_unload() が判定します。副作用を持たない純粋関数として切り出しているため、条件ごとの挙動をテストで確認できます。
判定は次の順番で行います。

  1. モデルが常駐しているか。常駐していなければ何もしない
  2. policyで自動解放が許可されているか。enabled: true で、かつ soft.allow に unload_gpu_model が含まれている場合に限って、自動解放を許可する
  3. アイドルか。GPU電力が40W以下で、かつGPU使用率が10%以下か
  4. アイドル状態が続いているか。20秒間隔で3回連続(計40秒)アイドルと判定されたか

4つの条件をすべて満たしたときに限って解放します。最後の連続判定(debounce)は、たまたま処理の切れ目を観測しただけでUnloadしてしまうのを防ぐためのものです。考え方は、アイドルだと確信できない場合はUnloadしないという安全側のルールです。
19時の off モードだけは例外で、アイドル状態を待ちません。業務時間外にオペレーターが明示的に解放を求めた、という扱いにして、モデルが常駐していれば解放します。
ここで使っているpolicyは、プログラムの中に直接書いた条件ではありません。第3部でMemoryに使ったObsidian Vaultの中に、memory/policy-hardware-aware.md というノートとして置いています。中身は、操作をHARDとSOFTに分けた一覧です。

HARD(禁止)

  • raise_power_limit
  • disable_thermal_throttling
  • overclock
  • kill_process

SOFT(条件付きで許可)

  • unload_gpu_model
  • reduce_context_size
  • switch_to_smaller_model

policyノートは、何かを起動するトリガーではありません。「この操作をしてよいか」を決める判定基準、いわばゲートです。

実行と検証:指示して終わりにしない

判定を通ると、hwagentの unload() が、Ollamaに keep_alive: 0 を指定した1トークンの生成を送り、モデルの解放を指示します。

keep_alive: 0 で1トークン生成
        ↓
Ollama /api/ps で常駐モデルを確認
        ↓
本当に解放されたかを検証
        ↓
logs/hwagent-power.log にJSONで記録
memory/hw-power-YYYY-MM-DD.md に追記

指示を出しただけで終わらせず、/api/ps で本当に解放されたかを確認します。結果は logs/hwagent-power.log にJSONで残し、still_resident: false のように解放できたかどうかも記録します。あわせて、Vaultの memory/hw-power-YYYY-MM-DD.md に日ごとの記録を追記します。電力の制御も、第3部で扱ったMemoryと同じ場所に経験として残る形です。

LLMはこのループに入っていない

ここまで読むと、AIが電力を管理しているように見えるかもしれません。しかし現時点では、この自動解放のループにLLMは入っていません。トリガーはcron、判定はpolicyノートと純粋関数、実行はhwagentで、すべて決まったルールで動いています。
それでも、構造としてはすでにMonitoringの一歩先にあります。

Metrics
  ↓
Observation
  ↓
Decision   ← policyによるゲート
  ↓
Action
  ↓
Verification

LLMを使わなかったのは意図的です。最初からLLMに判断を任せるのではなく、まず決まったルールでこのループが安全に回ることを確かめる方が先だと考えました。LLMが関わるのは別のループで、推論の中で振り返り、何かを提案し、その提案をゲートに通すという仕組みの設計です。そこでも、今回のpolicyノートのように「してよいこと」と「してはいけないこと」を先に定義しておくことが、土台になると考えています。

この「トリガー」「判定と許可」「実行と検証」を分ける構造は、最初から設計図があったものではありません。Hermes Agentと、どうすれば安全にUnloadできるかを相談しながら形にしていきました。debounceを入れることや、判定を純粋関数として切り出してテストできるようにすることなどは、Agentとのやり取りの中で出てきた案です。一方、policyをObsidian Vaultのノートとして置くことは、私から提案しました。第3部でMemoryとして使っているVaultに置けば、Agentの経験とハードウェアの運用ルールを同じ場所で管理できると考えたためです。

私の役割は、それぞれの案が自分の環境で安全か、意図した通りに動くかを確かめ、採用するかを決めることでした。Agentと相談しながら、Agentが動く箱の制御の仕組みを作る。第1部で書いた「Agentと対話しながら、Agentが働くためのHarnessを組み上げる」という進め方は、今回のハードウェアの話でも変わっていません。


5. Prometheus + Grafanaで「箱の状態」を時系列で見る

hwagentでメトリクスを取り出せるようになっても、そのままでは「今この瞬間の値」しか分かりません。1日の変化を追ったり、モデルをUnloadした前後を比較したりするには、値を時系列で保存し、グラフとして見られるようにする必要があります。そこで、PrometheusとGrafanaを使いました。

Prometheusは、メトリクスを保存する時系列データベースのデファクトスタンダードです。各ノードが公開する /metrics を一定間隔で取りに行き(scrape)、時刻付きのデータとして保存します。対象側が送るのではなく、Prometheus側が取りに行く「pull型」の仕組みのため、hwagentのようなexporterは /metrics を公開しておくだけで済みます。本環境では、hwagentとBeylaの二つを取得対象とし、30日分を保持しています。保存したデータは、PromQLというクエリ言語で集計できます。Prometheus Query Language の略で、Prometheus のタイムシリーズデータを引くためのクエリ言語です。次に説明するGrafana パネルの裏も PromQL です。

Grafanaは、時系列データを可視化するツールのデファクトスタンダードです。Prometheusをデータソースとして読み込み、グラフやゲージなどのパネルを描きます。今回は、GPU電力、モデル常駐状態、ネットワーク流量などを「HW — GB10」というパネル群にまとめました。
今回の環境では3000、9090、9091などのポートを既に別の用途で使っていたため、Grafanaは3100、Prometheusは9095としています。
これにより、Agentの内側で何が起きているかとは別に、AIを動かしている箱で何が起きているかを、時間の流れとともにGrafanaで確認できるようになりました。

図3. Grafanaダッシュボード1
図3.今回作成したGrafanaダッシュボード1:GPU・CPUの電力・温度、Ollamaモデルのメモリresident状態、およびその節電効果(画面の表示期間はLast 7 days。電力比較のステータスカードは直近24時間を集計)

なお、図3の「Power while 27b resident」は、瞬間値ではなく、hw_ollama_resident=1だった時間だけを対象にした24時間窓での時間加重平均です。そのため、本文中の74W前後という値とは意味が異なります。74W前後は推論時に観測した高い電力値であるのに対し、ダッシュボードの24.5Wはアイドル時間を含むモデル常駐中の平均電力です。「Saved」もこの常駐中平均とUnload中平均の差から算出しています。

以下は同期間でのネットワーク流量などのダッシュボードです。

図4. Grafanaダッシュボード2

図4.今回作成したGrafanaダッシュボード2:通信量(NIC I/O)、確立済みTCP接続数、eBPFによるフローバイト数、RTT分位値、TCP再送率(画面の表示期間はLast 7 days)


6. 電力グラフに「モデルの存在」が現れる

今回特に分かりやすかったのが、GPU Powerのグラフでした。モデル常駐中でも電力は一定ではなく、アイドル時には低い状態で推移する一方、推論が走るタイミングでは74W前後まで上昇する様子が確認できました。モデルをUnloadすると11.8W前後まで低下します。

74W
  │
  └───────────────┐
                  │
                  │ 11.8W
                  └──────────

この段差は、単なる電力監視以上の意味を持っていると感じました。モデルのライフサイクルが、電力という別の物理量に現れているためです。

Model lifecycle
       ↓
GPU memory
       ↓
GPU power
       ↓
Electricity

この因果関係を実測データとして追えることが分かると、ローカルAIのObservabilityでは、CPU使用率やGPU使用率を見るだけでは足りないと考えるようになりました。モデルが載っているかどうかも、重要なメトリクスです。そこで、hw_ollama_resident のように、モデルの常駐状態もメトリクスとして持たせることにしました。既製のexporterではなくhwagentを自作した理由も、ここにあります。


7. PromQLで分かった、「観測できなかった」と「0だった」の違い

ダッシュボードを作る過程では、PromQLで少しつまずきました。
GPU Powerのメトリクスには machine や gpu などのラベルがありますが、hw_ollama_resident にはGPUラベルがありません。そのまま掛け合わせると、期待した結果になりません。そこで、マシン単位で状態を結合するために次の記述を使いました。

on(machine) group_left()

Grafanaの設定画面を触っているだけでは見えにくい部分ですが、実際に組んでみると、メトリクスが存在することと、意味のある形でメトリクスを組み合わせられることは別だと分かります。

24時間の平均電力を出すときにも注意が必要でした。モデルがUnloadされている時間を0Wとして扱うと、実際には測定対象になっていない時間までゼロとして平均に含まれてしまいます。そこで、sum_over_time(...[24h:15s]) などを使って常駐状態によるマスクをかけ、データが存在しない場合は0ではなく NaN や -- として表示するようにしました。

細かい話に見えますが、ここは重要だと感じました。「観測できなかった」と「0だった」は違います。Observabilityを実際に組み立ててみると、この区別を意識せざるを得なくなります。


8. 電力の次に、通信を見る:Grafana BeylaとeBPF

GPUの電力が見えるようになると、次に気になったのはネットワークでした。ローカルAIといっても、実際にはAPI、Agent、MCP、Web UI、外部サービス、Dockerコンテナ、LLMサーバーなど、多くの要素が通信しています。
通信の観測には、Grafana Beylaを使いました。Grafana Labsが提供するネットワーク・アプリケーション観測ツールで、eBPFを使って計測した結果をPrometheusメトリクスとして出力します。
eBPFは、実行中のLinuxカーネルに小さなプログラムを安全に載せて動かす仕組みです。カーネルに読み込む前に検証器(verifier)がプログラムをチェックするため、カーネルモジュールを自作するよりも安全に、カーネル内部の処理へフックできます。これにより、ユーザー空間からは直接触れられないカーネルのネットワークスタックの中で、パケットの流れをそのまま計測できます。
ただし、eBPFのプログラムを自分で書いてカーネルに載せるのは、それなりに手間がかかります。Beylaは、その「eBPFを書く・載せる」部分を標準部品にしたツールだと捉えると分かりやすいと思います。利用者はYAMLで何を観測するかを指定するだけで、結果をPrometheus形式のメトリクスとして受け取れます。

今回の環境ではBeyla v3.36を使い、次の最小構成にしました。

network:
  enable: true
  source: tc
prometheus_export:
  port: 9110
  path: /metrics

Docker側では、ホストのネットワークとプロセスを観測できるように次のように設定しました。

network_mode: host
pid: host

権限については、--privileged を付けずに、必要なcapabilityとして BPF、PERFMON、NET_RAW、NET_ADMIN の4つだけを付与しました。今回のkernel 6.17 / aarch64環境では、これで動作しています。とりあえずprivilegedにするのは簡単ですが、何が必要なのかを切り分けておく方が、セキュリティ面でも望ましいと考えています。


9. eBPFで見えたもの、見えなかったもの

Beylaからは、beyla_network_flow_bytes_total などのネットワークフローメトリクスを取得でき、direction=request、direction=response、direction=unknown といった方向別の通信量も確認できました。
ある時点の値は次の通りでした。

request   ≒ 3.6e7
response  ≒ 3.1e8
unknown   ≒ 3.5e8

この時点のDGX Sparkは、自分から大量のリクエストを送る箱というより、何かに対して応答や中継を行う側として振る舞っていたことになります。もちろん、これはこの時点、この構成での実測結果であり、DGX Spark一般の性質を示すものではありません。重要なのは、ネットワークを見えるようにしたことで、それまで意識していなかった箱の役割が見えてきたことでした。

一方で、見えなかったものもあります。最初は、eBPFであれば通信の詳細まで全部見えるのではないかと考えていました。しかし今回のbare-metal構成では、誰から誰へ、どのポートへ、どのプロセスが通信しているのかというendpoint-levelの情報を、期待した形では取得できませんでした。

Beylaのnetwork modeで network.attributes.select による詳細属性の指定も試しましたが、この環境では期待したようには機能しませんでした。この種の設定は、Kubernetesやapplication instrumentationなど、別の観測スコープでは利用できます。

つまり、「その機能が存在する」ことと、「自分の構成でその情報が取れる」ことは別でした。今回の検証の中でも、特に大事な学びだったと思います。

図5. 通信の「量」と「接続」を分けて見る
図5.通信の「量」と「接続」を分けて見る


10. 「箱」と「接続」を分けて観測する

最終的には、ネットワーク観測を一つのツールで解決しようとするのをやめました。
Beylaは、この箱でネットワークがどの程度動いているかを、方向別に見るセンサーとして使います。一方、接続単位の確認はhwagentの network_status で行い、psutil や ss -ti を使ってTCP接続を確認し、peer、RTT、retransを見ます。こちらもhwagentの他のメトリクスと同じく、:9464からPrometheusへ渡しています。

# 観測の軸 使うもの ポート 見ているもの
1 電力 / GPU / CPU hwagent :9464 Ollama常駐、電力、利用率など約50項目
2 通信(方向別) Grafana Beyla(eBPF) :9110 request / response / unknown別の流量
3 通信(接続単位) hwagent network_status :9464 TCP peer / RTT / retrans
4 時系列・可視化 Prometheus + Grafana :9095 / :3100 上記すべてをグラフ化

11. 見えるものを増やすほど、境界が見えてくる

電力とネットワークを観測してみて一番面白かったのは、見えるようになったこと以上に、何が見えないのかが明確になったことでした。

GPUについては、電力、モデルの常駐状態、GPUの状態は見えます。しかし、なぜそのモデルが今必要なのかは、メトリクスからは分かりません。ネットワークについても、通信量、方向、TCP状態、RTT、retransは見えます。しかし、その通信がAgentのどの判断によって発生したのかまでは分かりません。

整理すると、次のようになります。

Hardware   GPU / Power
    ↓
Network    Flow / RTT / Retrans
    ↓
Process    Connection / Runtime
    ↓
Agent      Tool / Trace / Session
    ↓
Decision   なぜ、その行動を選んだのか

下のレイヤーほど、従来のシステムメトリクスで「何が起きたか」を把握しやすくなります。一方、上のレイヤーへ行くほど、単純なシステムメトリクスでは意味が切れていき、「なぜ起きたか」を説明するにはAgent側のTraceやSessionなどの情報が必要になります。
第3部で扱ったTraceやMemoryは、主に上側のレイヤーを説明するためのものでした。今回のHardwareやNetworkの観測は、下側のレイヤーを説明するためのものです。この二つをつなげて初めて、AIシステムの状態を説明できるようになるのだと考えています。
その意味で、Observabilityは「全部を見る」ことではなく、「どのレイヤーまで説明できるか」を設計することなのだと思います。

図6. Traceとメトリクスで説明できる範囲
図6. Traceとメトリクスで説明できる範囲


12. 数日動かして初めて分かったこと

今回の取り組みで一番大きかったのは、GPUの消費電力を測れたことそのものではありません。ローカルAIでは、モデルを動かすだけでなく、モデルを「置いておく」ことまで運用対象になると分かったことです。

クラウドAPIでは、モデルの常駐やGPUの電力を基本的に意識しません。しかし、自分の箱でモデルを動かすと、次のライフサイクルそのものがシステム設計の対象になります。

モデルをロードする
↓
GPUメモリを占有する
↓
電力を消費する
↓
待機する
↓
必要がなくなる
↓
Unloadする

そして、その判断には、Observation、Policy、Decision、Action、Verificationというループが必要になります。これは、これまでAgentやHarnessについて考えてきたことと、同じ構造です。


結び:Observeできなければ、Optimizeもできない

今回のDGX Sparkでは、自作のhwagentで箱の状態を数値にし、Grafana BeylaでeBPFによる通信量を取り出しました。それらをPrometheusに集め、Grafanaで一つの観測系にまとめています。GPU電力、モデル常駐、ネットワークフロー、TCP状態が、同じダッシュボードの上に並んだことになります。

実際に数日動かしてみて分かったのは、ローカルAIの運用コストは推論時間だけでは決まらない、ということでした。モデルを載せている時間、GPUを待機させている時間、ネットワークを流れているデータ、バックグラウンドで動いているAgent。それらを含めて、初めて「AIシステムの状態」になります。

今回の経験を通して、自分の中ではAIシステムの構造が、少しずつ次のようなループとして見えてきました。

Observe
↓
Decide
↓
Act
↓
Verify
↓
Observe again

何が起きているのかを見る。何をするか決める。実際に動かす。結果を検証する。そして、もう一度観測する。本稿で扱ったのは、その最初のObserveの部分です。

Observeできなければ、Optimizeもできません。

逆に言えば、観測できるようになって初めて、何をどう判断するかを考えられるようになります。どのモデルを載せておくのか、どのリクエストをどこへ渡すのか、どこまでをAIに判断させ、どこからを人間が決めるのか。DGX Sparkを動かしてみて、次に考えるべきことが少しずつ見えてきました。

振り返ると、このシリーズは一体のHermes Agentから始まりました。第1部ではAgentを二体にして議論させ、第2部ではMCPで外部の能力をつなぎ、第3部ではその経験を記録して検証できるようにしました。どれも、Agentをどう動かすかという、Harnessの内側の話でした。

今回はその外側に出て、Agentを動かしている箱そのものを見ました。GPUの電力、モデルの常駐、ネットワークの流れです。最初は「電力くらい測っておこう」という軽い気持ちでした。しかし実際に数日分のグラフを眺めていると、モデルを載せておくだけで電力を使うこと、GPUを自由には制御できないこと、eBPFでも見えないものがあることなど、動かしているだけでは気づかなかったことがいくつも出てきました。

Agentの内側を見るTraceと、箱の状態を見るメトリクス。その両方がそろって初めて、AIシステムの状態を説明できるようになります。

今回の第4部では、その下側を見えるようにするところまでを扱いました。見えるようになったことで、次に何を決めなければならないのかも見えてきています。そこから先は、また手を動かしながら確かめていきたいと思います。
ここまで読んでいただき、ありがとうございました。


情報源・参考資料

本シリーズ

Prometheus

Grafana

Grafana Beyla / eBPF

Ollama

既製のexporter(本文で比較対象として言及)

その他

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?