gpt-oss 運用監視ダッシュボード設計:Prometheus/Grafana/OTel で可観測性強化
gpt-oss の本番環境運用では、モデルの推論速度、リソース使用率、エラー率などをリアルタイムで監視することが不可欠です。せっかく高性能な LLM を構築しても、運用中のボトルネックやエラーを早期発見できなければ、ユーザー体験は大きく損なわれてしまいます。
本記事では、Prometheus、Grafana、OpenTelemetry(OTel)を組み合わせた可観測性基盤の設計方法を解説します。これらのツールを適切に組み合わせることで、メトリクス収集・可視化・アラート発行の仕組みを効率的に構築できるのです。
実際の運用現場で求められる監視項目の選定から、ダッシュボードの設計パターン、トラブルシューティング時の調査フローまで、実践的な内容をお届けします。
背景
LLM 運用における可観測性の重要性
大規模言語モデル(LLM)の運用は、従来の Web アプリケーションとは異なる特性を持ちます。推論処理は CPU/GPU の計算リソースを大量に消費し、レスポンスタイムは数百ミリ秒から数秒と幅があり、メモリ使用量も動的に変化します。
これらの特性により、LLM の運用では以下のような可観測性が求められます。
| 監視領域 | 主要メトリクス | 監視目的 | アラート閾値例 | |#|#|#|#| | 1 | パフォーマンス | レイテンシ、スループット | ユーザー体験維持 | P95 > 3 秒 | | 2 | リソース | GPU 使用率、メモリ使用量 | リソース枯渇防止 | GPU > 90% | | 3 | エラー率 | HTTP 5xx、モデルエラー | 可用性確保 | エラー率 > 5% | | 4 | モデル品質 | トークン生成速度、出力品質 | 品質維持 | 生成速度 < 10 tok/s |
従来のアプリケーション監視では捉えきれない、LLM 特有の動作パターンを可視化する必要があります。
可観測性を構成する三つの柱
現代の可観測性(Observability)は、以下の三つの柱で構成されます。
以下の図は、可観測性を構成する三つの要素とその関係性を示しています。
mermaidflowchart TB
obs[可観測性<br/>Observability]
obs --> metrics[メトリクス<br/>Metrics]
obs --> logs[ログ<br/>Logs]
obs --> traces[トレース<br/>Traces]
metrics --> prom[Prometheus<br/>時系列データ収集]
logs --> loki[Loki / Elasticsearch<br/>構造化ログ管理]
traces --> jaeger[Jaeger / Tempo<br/>分散トレーシング]
prom --> grafana[Grafana<br/>統合可視化]
loki --> grafana
jaeger --> grafana
grafana --> dashboard[ダッシュボード]
grafana --> alert[アラート]
style obs fill:#e1f5fe
style grafana fill:#fff9c4
style dashboard fill:#c8e6c9
style alert fill:#ffccbc
図で理解できる要点:
- 可観測性は三つの異なる視点からシステムを理解する
- それぞれ専用の収集・保存ツールが存在する
- Grafana が統合可視化レイヤーとして機能する
メトリクス(Metrics) は、数値データの時系列変化を記録します。CPU 使用率、リクエスト数、レスポンスタイムなど、定量的な指標を扱います。
ログ(Logs) は、システムが出力するイベント記録です。エラーメッセージ、デバッグ情報、トランザクション記録などが含まれます。
トレース(Traces) は、一つのリクエストがシステム内をどう流れたかを追跡します。分散システムでのボトルネック特定に有効です。
Prometheus / Grafana / OpenTelemetry の役割分担
これら三つのツールは、それぞれ異なる役割を担いながら連携します。
typescript// 各ツールの役割を TypeScript の型定義で表現
interface ObservabilityStack {
// Prometheus: メトリクス収集・保存
prometheus: {
role: 'メトリクス収集エンジン';
capabilities: string[];
storage: 'TSDB(時系列データベース)';
};
// Grafana: 可視化・ダッシュボード
grafana: {
role: 'データ可視化プラットフォーム';
datasources: string[];
outputs: string[];
};
// OpenTelemetry: 計装・データ収集
opentelemetry: {
role: '計装フレームワーク';
signals: string[];
exporters: string[];
};
}
上記の型定義は、各ツールの責務を明確に示しています。
Prometheus は、定期的にアプリケーションからメトリクスを収集(Pull 型)し、時系列データベースに保存します。シンプルで信頼性の高いアーキテクチャが特徴です。
Grafana は、複数のデータソース(Prometheus、Loki、Jaeger など)からデータを取得し、美しいダッシュボードとして可視化します。アラート機能も提供します。
OpenTelemetry(OTel) は、アプリケーションコードに埋め込む計装ライブラリです。メトリクス・ログ・トレースを統一的な方法で収集し、様々なバックエンド(Prometheus、Jaeger など)にエクスポートできます。
課題
LLM 特有の監視指標の複雑性
gpt-oss の運用では、従来の Web アプリケーションとは異なる監視指標が必要になります。単純なリクエスト/レスポンスだけでなく、モデル推論の内部状態まで可視化する必要があるのです。
以下の図は、LLM アプリケーションの処理フローと各段階で監視すべき指標を示しています。
mermaidflowchart LR
user[クライアント] -->|1.リクエスト| lb[ロードバランサ]
lb -->|2.振り分け| api[API サーバー]
api -->|3.前処理| preproc[トークナイズ]
preproc -->|4.推論| model[LLM モデル]
model -->|5.後処理| postproc[デコード]
postproc -->|6.レスポンス| user
lb -.監視.- m1[["接続数<br/>帯域幅"]]
api -.監視.- m2[["リクエスト数<br/>エラー率"]]
preproc -.監視.- m3[["トークン数<br/>処理時間"]]
model -.監視.- m4[["GPU使用率<br/>推論時間<br/>バッチサイズ"]]
postproc -.監視.- m5[["生成トークン数<br/>品質スコア"]]
style model fill:#ffccbc
style m4 fill:#fff9c4
図で理解できる要点:
- LLM アプリケーションは複数の処理段階で構成される
- 各段階で異なる種類のメトリクスが必要
- モデル推論フェーズが最も複雑で重要な監視対象
特に以下の指標は LLM 運用で重要です。
python# LLM 固有のメトリクス定義例
from prometheus_client import Counter, Histogram, Gauge
# トークン処理関連
tokens_processed = Counter(
'llm_tokens_processed_total',
'Total number of tokens processed',
['model_name', 'operation'] # ラベルでモデル名と処理タイプを分類
)
上記のコードは、処理されたトークン数を累積カウントするメトリクスを定義しています。
python# 推論時間のヒストグラム
inference_duration = Histogram(
'llm_inference_duration_seconds',
'Time spent in model inference',
['model_name', 'batch_size'],
buckets=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0] # レスポンスタイムのバケット
)
ヒストグラムを使うことで、レイテンシの分布(P50、P95、P99 など)を計算できるようになります。
python# GPU メモリ使用量
gpu_memory_used = Gauge(
'llm_gpu_memory_bytes',
'GPU memory usage in bytes',
['gpu_id', 'model_name']
)
これらのメトリクスを適切に定義し、収集することが LLM 監視の第一歩です。
分散システムにおけるトレーシングの困難さ
gpt-oss を本番運用する際、多くの場合は分散アーキテクチャを採用します。API ゲートウェイ、複数のモデルサーバー、キャッシュレイヤー、データベースなど、複数のコンポーネントが連携します。
この環境では、一つのユーザーリクエストが複数のサービスを横断します。レイテンシが高い場合、どのコンポーネントがボトルネックなのかを特定するのが困難になるのです。
typescript// 分散トレーシングの課題を示す型定義
interface DistributedRequest {
traceId: string; // リクエスト全体を識別する ID
spans: RequestSpan[]; // 各サービスでの処理単位
}
interface RequestSpan {
spanId: string; // この処理単位の ID
parentSpanId?: string; // 親の処理単位 ID
serviceName: string; // サービス名
operationName: string; // 処理名
startTime: number; // 開始時刻
duration: number; // 処理時間
tags: Record<string, any>; // メタデータ
logs: LogEntry[]; // この処理内のログ
}
上記の型定義は、分散トレーシングで必要となるデータ構造を示しています。
typescript// 問題:各サービスで独自にログを出力すると
// リクエスト全体の流れが見えにくくなる
interface ServiceLog {
timestamp: string;
level: string;
message: string;
// traceId が無いと、このログがどのリクエストのものか不明
}
上記のコードが示すように、トレース ID とスパン ID を適切に管理しないと、リクエストの全体像が見えなくなります。
ダッシュボードの情報過多とアラート疲労
監視項目が増えると、ダッシュボードに表示する情報が増え、かえって重要な情報が埋もれてしまう「情報過多」の問題が発生します。
同様に、アラート閾値を厳しく設定しすぎると、頻繁にアラートが発火し、運用担当者が麻痺してしまう「アラート疲労」も深刻な課題です。
| 問題 | 症状 | 影響 | 対策の方向性 | |#|#|#|#| | 1 | 情報過多 | ダッシュボードに 50+ のグラフ | 重要指標の見落とし | レイヤー分け、目的別分割 | | 2 | アラート疲労 | 1 日 100+ のアラート通知 | 真の障害を見逃す | 閾値の適正化、集約 | | 3 | 文脈欠如 | 数値だけ表示 | 原因特定に時間がかかる | 関連メトリクスの併記 | | 4 | 更新遅延 | データ反映に 5 分以上 | リアルタイム対応不可 | スクレイプ間隔の短縮 |
これらの課題を解決するには、設計段階から「誰が、何のために見るのか」を明確にする必要があります。
解決策
OpenTelemetry による統一的な計装設計
OpenTelemetry(OTel)を使うことで、メトリクス・ログ・トレースを統一的な方法で収集できます。コードへの埋め込み(計装)も一度で済み、バックエンドの変更にも柔軟に対応可能です。
以下は、gpt-oss の推論エンドポイントに OTel を組み込む例です。
typescriptimport {
trace,
metrics,
context,
} from '@opentelemetry/api';
import {
MeterProvider,
PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
// Prometheus エクスポーターの設定
const prometheusExporter = new PrometheusExporter(
{
port: 9464, // Prometheus がスクレイプするポート
},
() => {
console.log('Prometheus exporter started on port 9464');
}
);
上記のコードは、Prometheus にメトリクスを公開するエクスポーターを初期化しています。
typescript// メトリクスプロバイダーの設定
const meterProvider = new MeterProvider({
readers: [
new PeriodicExportingMetricReader({
exporter: prometheusExporter,
exportIntervalMillis: 1000, // 1秒ごとにメトリクスを更新
}),
],
});
// グローバルに設定
metrics.setGlobalMeterProvider(meterProvider);
const meter = metrics.getMeter('gpt-oss-inference');
メトリクスプロバイダーを設定し、1 秒間隔でメトリクスを更新するようにしています。
typescript// 推論処理のメトリクス定義
const inferenceCounter = meter.createCounter(
'llm.inference.requests',
{
description: 'Number of inference requests',
}
);
const inferenceHistogram = meter.createHistogram(
'llm.inference.duration',
{
description: 'Inference duration in seconds',
unit: 'seconds',
}
);
const tokensProcessed = meter.createCounter(
'llm.tokens.processed',
{
description: 'Total tokens processed',
}
);
ここでは、推論リクエスト数、推論時間、トークン数という三つの重要なメトリクスを定義しています。
typescript// 実際の推論処理への計装
export async function runInference(
modelName: string,
prompt: string
): Promise<string> {
const startTime = Date.now();
// トレーシングのためのスパン作成
const tracer = trace.getTracer('gpt-oss-inference');
const span = tracer.startSpan('inference', {
attributes: {
'model.name': modelName,
'prompt.length': prompt.length,
},
});
try {
// 推論処理の実行
const result = await executeModel(modelName, prompt);
// メトリクスの記録
const duration = (Date.now() - startTime) / 1000;
inferenceCounter.add(1, {
model: modelName,
status: 'success',
});
inferenceHistogram.record(duration, {
model: modelName,
});
tokensProcessed.add(result.tokenCount, {
model: modelName,
});
// スパンに結果を記録
span.setAttributes({
'response.token_count': result.tokenCount,
'response.duration_ms': duration * 1000,
});
span.setStatus({ code: 1 }); // OK
return result.text;
} catch (error) {
// エラー時のメトリクス
inferenceCounter.add(1, {
model: modelName,
status: 'error',
});
span.setStatus({ code: 2, message: error.message }); // ERROR
span.recordException(error);
throw error;
} finally {
span.end();
}
}
この関数は、推論処理の前後でメトリクスを記録し、トレースのスパンも作成しています。エラー時にも適切に記録される仕組みです。
Prometheus によるメトリクス収集設計
OpenTelemetry が公開したメトリクスを、Prometheus が定期的に収集(スクレイプ)します。
以下は、Prometheus の設定ファイル例です。
yaml# prometheus.yml
# グローバル設定
global:
scrape_interval: 15s # 15秒ごとにメトリクスを収集
evaluation_interval: 15s # アラートルールの評価間隔
# すべてのメトリクスに自動付与されるラベル
external_labels:
cluster: 'gpt-oss-production'
region: 'ap-northeast-1'
グローバル設定では、収集間隔とすべてのメトリクスに付与されるラベルを定義します。
yaml# アラートマネージャーの設定
alerting:
alertmanagers:
- static_configs:
- targets:
- 'alertmanager:9093'
アラート通知を処理する Alertmanager の接続先を設定しています。
yaml# アラートルールファイルの読み込み
rule_files:
- '/etc/prometheus/rules/*.yml'
アラートルールは別ファイルで管理し、ここで読み込みます。
yaml# スクレイプ対象の設定
scrape_configs:
# gpt-oss API サーバーからのメトリクス収集
- job_name: 'gpt-oss-api'
static_configs:
- targets:
- 'gpt-oss-api-1:9464'
- 'gpt-oss-api-2:9464'
- 'gpt-oss-api-3:9464'
relabel_configs:
# インスタンスラベルをホスト名に変更
- source_labels: [__address__]
regex: '([^:]+):.*'
target_label: instance
replacement: '$1'
各 API サーバーのメトリクスエンドポイントを定義しています。relabel_configs でラベルをカスタマイズできます。
yaml# GPU メトリクスの収集(nvidia-gpu-exporter 使用)
- job_name: 'gpu-metrics'
static_configs:
- targets:
- 'gpu-node-1:9445'
- 'gpu-node-2:9445'
metric_relabel_configs:
# 不要なメトリクスをドロップ
- source_labels: [__name__]
regex: 'nvidia_gpu_power_state'
action: drop
GPU ノードからは専用の Exporter でメトリクスを収集します。不要なメトリクスはドロップして保存容量を節約します。
yaml# Node Exporter(サーバーの基本メトリクス)
- job_name: 'node-exporter'
static_configs:
- targets:
- 'node-1:9100'
- 'node-2:9100'
- 'node-3:9100'
CPU、メモリ、ディスクなどのシステムメトリクスも収集します。
Grafana ダッシュボード設計のベストプラクティス
Grafana では、収集したメトリクスを視覚的にわかりやすく表示します。ダッシュボード設計では、「誰が見るのか」を明確にし、目的に応じて複数のダッシュボードを作成するのがベストプラクティスです。
レイヤー別ダッシュボード構成
以下の図は、推奨されるダッシュボードの階層構造を示しています。
mermaidflowchart TD
db[ダッシュボード階層]
db --> exec[エグゼクティブ<br/>ダッシュボード]
db --> ops[運用<br/>ダッシュボード]
db --> dev[開発<br/>ダッシュボード]
exec --> exec1[["全体稼働率<br/>SLA達成率<br/>コスト推移"]]
ops --> ops1[["リアルタイム監視<br/>アラート一覧<br/>リソース使用率"]]
ops --> ops2[["ログ分析<br/>エラー追跡<br/>パフォーマンス"]]
dev --> dev1[["APIレスポンス<br/>モデル精度<br/>トークン統計"]]
dev --> dev2[["デバッグ情報<br/>トレース詳細<br/>キャッシュ効率"]]
style exec fill:#e1f5fe
style ops fill:#fff9c4
style dev fill:#c8e6c9
図で理解できる要点:
- 見る人の役割に応じてダッシュボードを分ける
- エグゼクティブ層は高レベルな KPI のみ
- 運用チームはリアルタイムな詳細情報
- 開発チームは技術的な深掘り情報
パネル構成の実装例
以下は、Grafana のダッシュボード定義(JSON 形式)の一部です。実際には Grafana UI で作成することが多いですが、コードで管理することも可能です。
json{
"dashboard": {
"title": "gpt-oss 運用監視ダッシュボード",
"tags": ["llm", "production", "monitoring"],
"timezone": "Asia/Tokyo",
"panels": [
{
"id": 1,
"title": "リクエスト数(1分あたり)",
"type": "graph",
"datasource": "Prometheus",
"targets": [
{
"expr": "rate(llm_inference_requests_total[1m])",
"legendFormat": "{{model}} - {{status}}"
}
],
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
}
}
]
}
}
このパネルは、1 分あたりの推論リクエスト数を、モデル名とステータス(成功/失敗)別に表示します。
typescript// Grafana ダッシュボードを TypeScript で定義する例
// (grafonnet などのライブラリを使用)
interface GrafanaPanel {
title: string;
type: 'graph' | 'singlestat' | 'table' | 'heatmap';
queries: PrometheusQuery[];
layout: { x: number; y: number; w: number; h: number };
}
interface PrometheusQuery {
expr: string; // PromQL クエリ
legend: string; // 凡例のフォーマット
interval?: string; // データポイントの間隔
}
上記は、ダッシュボードをコードで管理するための型定義です。
typescript// リクエストレイテンシのパネル定義
const latencyPanel: GrafanaPanel = {
title: 'P50/P95/P99 レイテンシ',
type: 'graph',
queries: [
{
expr: 'histogram_quantile(0.50, rate(llm_inference_duration_bucket[5m]))',
legend: 'P50',
},
{
expr: 'histogram_quantile(0.95, rate(llm_inference_duration_bucket[5m]))',
legend: 'P95',
},
{
expr: 'histogram_quantile(0.99, rate(llm_inference_duration_bucket[5m]))',
legend: 'P99',
},
],
layout: { x: 12, y: 0, w: 12, h: 8 },
};
P50、P95、P99 のパーセンタイルレイテンシを表示するパネル定義です。ヒストグラムメトリクスから計算しています。
typescript// GPU 使用率のパネル
const gpuPanel: GrafanaPanel = {
title: 'GPU 使用率',
type: 'graph',
queries: [
{
expr: 'nvidia_gpu_utilization{job="gpu-metrics"}',
legend: 'GPU {{gpu_id}} - {{instance}}',
interval: '30s',
},
],
layout: { x: 0, y: 8, w: 12, h: 8 },
};
各 GPU の使用率を時系列グラフで表示します。
アラートルールの設計
Prometheus では、メトリクスに基づいてアラートを定義できます。
yaml# /etc/prometheus/rules/llm-alerts.yml
groups:
- name: llm_inference_alerts
interval: 30s # 30秒ごとにルールを評価
rules:
# 高レイテンシアラート
- alert: HighInferenceLatency
expr: |
histogram_quantile(0.95,
rate(llm_inference_duration_bucket[5m])
) > 3
for: 2m # 2分間継続したら発火
labels:
severity: warning
component: inference
annotations:
summary: '推論レイテンシが高い状態が継続しています'
description: 'P95 レイテンシが {{ $value }} 秒です(閾値: 3秒)'
P95 レイテンシが 3 秒を超える状態が 2 分間続いたら警告を発します。
yaml# エラー率アラート
- alert: HighErrorRate
expr: |
(
rate(llm_inference_requests_total{status="error"}[5m])
/
rate(llm_inference_requests_total[5m])
) > 0.05
for: 1m
labels:
severity: critical
component: inference
annotations:
summary: '推論エラー率が異常に高くなっています'
description: 'エラー率: {{ $value | humanizePercentage }}(閾値: 5%)'
エラー率が 5% を超えたら、より深刻な critical レベルのアラートとして発火します。
yaml# GPU メモリ不足アラート
- alert: GPUMemoryHigh
expr: |
nvidia_gpu_memory_used_bytes / nvidia_gpu_memory_total_bytes > 0.9
for: 5m
labels:
severity: warning
component: gpu
annotations:
summary: 'GPU メモリ使用率が 90% を超えています'
description: 'GPU {{ $labels.gpu_id }} on {{ $labels.instance }}: {{ $value | humanizePercentage }}'
GPU メモリ使用率が 90% を超えたら警告します。OOM(Out of Memory)を未然に防ぐためのアラートです。
具体例
Docker Compose による監視スタック構築
ここでは、gpt-oss の監視環境を Docker Compose で構築する具体的な手順を示します。
ディレクトリ構成
bashgpt-oss-monitoring/
├── docker-compose.yml # 全サービスの定義
├── prometheus/
│ ├── prometheus.yml # Prometheus 設定
│ └── rules/
│ └── llm-alerts.yml # アラートルール
├── grafana/
│ ├── provisioning/
│ │ ├── datasources/
│ │ │ └── prometheus.yml # データソース自動設定
│ │ └── dashboards/
│ │ ├── dashboard.yml # ダッシュボード自動読み込み
│ │ └── llm-dashboard.json # ダッシュボード定義
│ └── grafana.ini # Grafana 設定
└── otel-collector/
└── config.yml # OpenTelemetry Collector 設定
Docker Compose 設定
以下は、Prometheus、Grafana、OpenTelemetry Collector を起動する設定です。
yaml# docker-compose.yml
version: '3.8'
services:
# Prometheus サービス
prometheus:
image: prom/prometheus:v2.45.0
container_name: prometheus
ports:
- '9090:9090'
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- ./prometheus/rules:/etc/prometheus/rules
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
restart: unless-stopped
Prometheus コンテナの設定です。設定ファイルとアラートルールをマウントしています。
yaml# Grafana サービス
grafana:
image: grafana/grafana:10.0.3
container_name: grafana
ports:
- '3000:3000'
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/grafana.ini:/etc/grafana/grafana.ini
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_USERS_ALLOW_SIGN_UP=false
depends_on:
- prometheus
restart: unless-stopped
Grafana コンテナの設定です。初期管理者の認証情報を環境変数で設定しています。
yaml# OpenTelemetry Collector
otel-collector:
image: otel/opentelemetry-collector:0.82.0
container_name: otel-collector
ports:
- '4317:4317' # OTLP gRPC receiver
- '4318:4318' # OTLP HTTP receiver
- '8888:8888' # Prometheus metrics from collector itself
volumes:
- ./otel-collector/config.yml:/etc/otel/config.yml
command: ['--config=/etc/otel/config.yml']
depends_on:
- prometheus
restart: unless-stopped
OpenTelemetry Collector は、アプリケーションからメトリクス・トレースを受け取り、Prometheus などにエクスポートします。
yaml # Node Exporter(ホストメトリクス収集)
node-exporter:
image: prom/node-exporter:v1.6.1
container_name: node-exporter
ports:
- "9100:9100"
command:
- '--path.rootfs=/host'
volumes:
- '/:/host:ro,rslave'
restart: unless-stopped
volumes:
prometheus-data:
grafana-data:
Node Exporter は、CPU、メモリ、ディスクなどのシステムメトリクスを収集します。
OpenTelemetry Collector 設定
以下は、OTel Collector の設定ファイルです。
yaml# otel-collector/config.yml
receivers:
# OTLP 受信(gRPC と HTTP)
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# Prometheus メトリクス受信
prometheus:
config:
scrape_configs:
- job_name: 'otel-collector'
scrape_interval: 10s
static_configs:
- targets: ['0.0.0.0:8888']
アプリケーションから OTLP プロトコルでデータを受信する設定です。
yamlprocessors:
# バッチ処理(効率化のため)
batch:
timeout: 10s
send_batch_size: 1024
# メモリ制限
memory_limiter:
check_interval: 1s
limit_mib: 512
受信したデータをバッチ処理し、メモリ使用量を制限します。
yamlexporters:
# Prometheus へのエクスポート
prometheus:
endpoint: '0.0.0.0:8889'
namespace: 'otel'
# ログ出力(デバッグ用)
logging:
loglevel: info
メトリクスを Prometheus 形式で公開します。
yamlservice:
pipelines:
# メトリクスパイプライン
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch]
exporters: [prometheus, logging]
# トレースパイプライン
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [logging]
受信したデータをどう処理してどこにエクスポートするかを定義しています。
gpt-oss アプリケーションへの計装実装
ここでは、実際の gpt-oss API サーバーに OpenTelemetry を組み込む例を示します。
Node.js / Express での実装
typescript// src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
計装に必要な各種エクスポーターをインポートします。
typescript// Prometheus メトリクスエクスポーターの設定
const prometheusExporter = new PrometheusExporter({
port: 9464,
});
// トレースエクスポーターの設定(OTel Collector へ送信)
const traceExporter = new OTLPTraceExporter({
url: 'http://otel-collector:4318/v1/traces',
});
Prometheus へのメトリクス公開と、OTel Collector へのトレース送信を設定します。
typescript// OpenTelemetry SDK の初期化
const sdk = new NodeSDK({
// 自動計装(Express、HTTP、etc.)
instrumentations: [getNodeAutoInstrumentations()],
// メトリクスリーダーの設定
metricReader: new PeriodicExportingMetricReader({
exporter: prometheusExporter,
exportIntervalMillis: 1000,
}),
// トレースエクスポーターの設定
traceExporter: traceExporter,
// サービス情報
serviceName: 'gpt-oss-api',
});
// SDK の開始
sdk.start();
SDK を初期化し、自動計装を有効化します。
typescript// プロセス終了時のクリーンアップ
process.on('SIGTERM', () => {
sdk
.shutdown()
.then(() =>
console.log(
'OpenTelemetry SDK shut down successfully'
)
)
.catch((error) =>
console.error('Error shutting down SDK', error)
)
.finally(() => process.exit(0));
});
export default sdk;
プロセス終了時に適切にシャットダウンします。
カスタムメトリクスの追加
自動計装では捉えられない LLM 固有のメトリクスを追加します。
typescript// src/metrics/llm-metrics.ts
import { metrics } from '@opentelemetry/api';
const meter = metrics.getMeter('gpt-oss-custom-metrics');
// カスタムメトリクスの定義
export const llmMetrics = {
// トークン処理数
tokensProcessed: meter.createCounter(
'llm.tokens.processed',
{
description: 'Total tokens processed by the model',
unit: 'tokens',
}
),
// プロンプトトークン数
promptTokens: meter.createHistogram('llm.tokens.prompt', {
description: 'Number of tokens in prompts',
unit: 'tokens',
}),
// 完了トークン数
completionTokens: meter.createHistogram(
'llm.tokens.completion',
{
description: 'Number of tokens in completions',
unit: 'tokens',
}
),
// モデル読み込み時間
modelLoadTime: meter.createHistogram(
'llm.model.load_time',
{
description: 'Time to load model into memory',
unit: 'seconds',
}
),
// 同時実行中のリクエスト数
activeRequests: meter.createUpDownCounter(
'llm.requests.active',
{
description:
'Number of requests currently being processed',
}
),
};
LLM 運用に必要なカスタムメトリクスを定義します。
API エンドポイントへの計装
typescript// src/routes/inference.ts
import express from 'express';
import { trace, context } from '@opentelemetry/api';
import { llmMetrics } from '../metrics/llm-metrics';
const router = express.Router();
const tracer = trace.getTracer('gpt-oss-api');
// 推論エンドポイント
router.post('/v1/inference', async (req, res) => {
const { model, prompt, max_tokens = 100 } = req.body;
// アクティブリクエスト数をインクリメント
llmMetrics.activeRequests.add(1, { model });
// トレーシング用のスパン作成
const span = tracer.startSpan('inference_request', {
attributes: {
'model.name': model,
'request.max_tokens': max_tokens,
'prompt.length': prompt.length,
},
});
const startTime = Date.now();
推論エンドポイントに計装を追加します。リクエスト開始時にメトリクスとスパンを記録開始します。
typescript try {
// プロンプトのトークナイズ
const tokenizeSpan = tracer.startSpan('tokenize', {
parent: span,
});
const promptTokens = await tokenizePrompt(prompt);
llmMetrics.promptTokens.record(promptTokens.length, { model });
tokenizeSpan.end();
// モデル推論
const inferenceSpan = tracer.startSpan('model_inference', {
parent: span,
});
const completion = await runModelInference(model, promptTokens, max_tokens);
inferenceSpan.end();
// トークン数の記録
const totalTokens = promptTokens.length + completion.tokens.length;
llmMetrics.tokensProcessed.add(totalTokens, { model });
llmMetrics.completionTokens.record(completion.tokens.length, { model });
処理の各段階でメトリクスを記録し、スパンで追跡します。
typescript // レスポンス
res.json({
text: completion.text,
usage: {
prompt_tokens: promptTokens.length,
completion_tokens: completion.tokens.length,
total_tokens: totalTokens,
},
model: model,
});
span.setStatus({ code: 1 }); // OK
} catch (error) {
span.recordException(error);
span.setStatus({ code: 2, message: error.message });
res.status(500).json({ error: error.message });
} finally {
// アクティブリクエスト数をデクリメント
llmMetrics.activeRequests.add(-1, { model });
span.end();
}
});
export default router;
エラー時にも適切にメトリクスを記録し、finally ブロックでリソースをクリーンアップします。
ダッシュボードでの可視化例
最後に、Grafana でどのように可視化するかを示します。
メインダッシュボードの構成
以下のようなパネル構成を推奨します。
| パネル名 | 種類 | 表示内容 | PromQL クエリ例 |
|#|#|#|#|
| 1 | リクエスト数 | Graph | rate(http_requests_total[5m]) |
| 2 | エラー率 | Singlestat | rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) |
| 3 | レイテンシ | Graph | histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) |
| 4 | トークン処理数 | Counter | increase(llm_tokens_processed[1h]) |
| 5 | GPU 使用率 | Graph | nvidia_gpu_utilization |
| 6 | メモリ使用量 | Graph | process_resident_memory_bytes / 1024 / 1024 |
PromQL クエリ実例
以下は、実際に使えるクエリ例です。
promql# 過去 5 分間のリクエスト成功率
(
rate(llm_inference_requests_total{status="success"}[5m])
/
rate(llm_inference_requests_total[5m])
) * 100
成功リクエストの割合をパーセントで表示します。
promql# モデル別の平均トークン生成速度(tokens/second)
rate(llm_tokens_completion[5m])
/
rate(llm_inference_requests_total{status="success"}[5m])
1 リクエストあたりの平均トークン生成数を計算します。
promql# GPU メモリ使用率
(
nvidia_gpu_memory_used_bytes
/
nvidia_gpu_memory_total_bytes
) * 100
GPU メモリ使用率をパーセントで表示します。
promql# P95 推論レイテンシ(モデル別)
histogram_quantile(0.95,
sum(rate(llm_inference_duration_bucket[5m])) by (le, model)
)
各モデルの P95 レイテンシを計算します。
アラート通知の設定
Grafana のアラート機能で、Slack や Email に通知を送ることができます。
json{
"alerting": {
"contactPoints": [
{
"name": "Slack - Engineering",
"type": "slack",
"settings": {
"url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
"text": "{{ .CommonAnnotations.summary }}\n{{ .CommonAnnotations.description }}"
}
}
],
"policies": [
{
"receiver": "Slack - Engineering",
"group_by": ["alertname", "severity"],
"group_wait": "30s",
"group_interval": "5m",
"repeat_interval": "12h"
}
]
}
}
Slack への通知設定例です。アラートが発火すると、指定した Webhook URL にメッセージが送信されます。
まとめ
本記事では、gpt-oss の運用監視ダッシュボードを Prometheus、Grafana、OpenTelemetry で構築する方法を解説しました。
可観測性の三つの柱(メトリクス・ログ・トレース)を理解し、それぞれに適したツールを組み合わせることで、LLM アプリケーションの運用品質を大きく向上させることができます。
特に重要なポイントは以下の通りです。
OpenTelemetry による統一的な計装で、コードの変更を最小限に抑えながらメトリクスとトレースを収集できます。一度計装すれば、バックエンドを変更しても対応できる柔軟性があります。
Prometheus の Pull 型アーキテクチャにより、シンプルで信頼性の高いメトリクス収集が実現できます。設定もシンプルで、スケールアウトも容易です。
Grafana のダッシュボード設計では、見る人の役割に応じて複数のダッシュボードを用意することが推奨されます。エグゼクティブ、運用チーム、開発チームで必要な情報は異なるためです。
アラートルールの適切な設計により、真に重要な障害だけを通知し、アラート疲労を防ぐことができます。閾値は運用しながら調整していくことが重要でしょう。
LLM 特有の監視指標(トークン処理数、GPU 使用率、推論レイテンシなど)を適切に収集し、可視化することで、パフォーマンスの問題を早期発見し、ユーザー体験を継続的に改善できます。
監視基盤は一度構築して終わりではありません。運用を続けながら、必要なメトリクスを追加し、ダッシュボードを改善し、アラート閾値を調整していくことが重要です。
関連リンク
articlegpt-oss 運用監視ダッシュボード設計:Prometheus/Grafana/OTel で可観測性強化
articlegpt-oss が OOM/VRAM 枯渇で落ちる:モデル分割・ページング・バッチ制御の解決策
articlegpt-oss の量子化別ベンチ比較:INT8/FP16/FP8 の速度・品質トレードオフ
articlegpt-oss でナレッジ検索アシスタント:根拠表示・更新検知・検索ログ最適化
articlegpt-oss で JSON 構造化出力を安定させる:スキーマ提示・検証リトライ・自動修復
articlegpt-oss のモデルルーティング設計:サイズ別・ドメイン別・コスト別の自動切替
articleNotebookLM 活用事例:営業提案書の下書きと顧客要件の整理を自動化
articleGrok RAG 設計入門:社内ドキュメント検索を高精度にする構成パターン
articlegpt-oss 運用監視ダッシュボード設計:Prometheus/Grafana/OTel で可観測性強化
articleNode.js 標準テストランナー完全理解:`node:test` がもたらす新しい DX
articleNext.js の Route Handlers で multipart/form-data が受け取れない問題の切り分け術
articleMCP サーバー で社内ナレッジ検索チャットを構築:権限制御・要約・根拠表示の実装パターン
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来