はじめに
今年に入ってから、Liquid AI社からリリースされた Small Language Model、LFM2シリーズを触る機会があり、CPU環境でも動くモデルが結構あるのね〜なんて思っていた今日このごろ。
Finetuningしてタスクトレイに常駐させるAIのNanoエージェント(風味)のアプリやWebブラウザのプラグインとしてtransformers.jsを使ったNanoアプリなんかをいじっていた矢先、Ciscoから時系列データのための基盤モデル 「Cisco Time Series Model (CTSM)」 が公開されました。
これは「Zero-shot」で予測が可能という強力なモデルですが、通常はGPUサーバーやDockerコンテナ(DSDL)が必要ですなのですが...
「もっと手軽に、Splunk Appを入れるだけでこの最新モデルを使えないか?」
...と思い立ち、Splunk Appの中に推論エンジンを内包し、インストール時に環境を自動構築して動かす挑戦をしてみしました。今回はそのアーキテクチャと実装の裏側を共有します。
(ひとりごと)
- とはいえ、Splunk向け汎用推論エンジンをコンテナで包んで、Splunk Appから設定だけ流し込むとかのほうが、環境が汚れなくっていいというのはありますが!
- ほぼAIに書かせてチロッと修正加えたくらい… お陰様で、庭の掃除とか子供と遊ぶ時間とかできて最高。いい時代になりました。(Splunkを仕事でつかったり関わったりしている立場ではないので仕事時間ではやれない)
今回作ったもの
Splunkの検索コマンド | ctsm を実行するだけで、CPU使用率などの時系列データをCiscoのAIモデルに入力し、未来の値を予測してグラフに描画するAppです。(今朝ダウンロードしたmacos版のSplunk Enterprise
特徴:
Zero-shot Forecasting: 追加学習なしで、いきなり高精度な予測が可能。
Zero-configuration: ユーザーはAppを入れるだけ。裏で(ある程度)勝手にPython仮想環境が作られます。
(おそらく)Cross-Platform: Windows / Mac / Linux すべてで動作(uvを活用)。
アーキテクチャ:SplunkとAIの共存
巨大なAIモデルをSplunkの検索プロセス(splunkd)内で直接ロードするのは、メモリ管理やライブラリ依存の観点から御法度です。 そこで、「Modular Input(Persistent Mode)」 を活用したサイドカー構成を採用しました。
起動管理: Splunkが起動すると、inputs.conf に定義されたManagerスクリプトが走り、推論サーバー(FastAPI)をサブプロセスとして起動・監視します。
環境分離: 推論サーバーは、Splunk内蔵Pythonではなく、独立した仮想環境(.venv)で動作させます。
通信: 検索コマンド(| ctsm)と推論サーバーは、localhostの動的ポート経由で通信します。
ちょっとしたポイント
1. uv による環境構築
AIモデルを動かすには torch や transformers が必要ですが、これらをzipで配布するとOS間の互換性で死にます。 そこで、Pythonパッケージマネージャー uv を採用しました。
Managerスクリプトは起動時に以下を行います。
uv コマンドの有無をチェック。
.venv がなければ、uv venv と uv pip install を実行し、そのOSに最適なバイナリを一瞬でダウンロード・構築します。
これにより、配布サイズを最小限に抑えつつ、(おそらく)クロスプラットフォーム対応を実現しました。
2. ポート競合の完全回避
「ポート8000番が埋まっていて動かない」というあるある問題を避けるため、推論サーバーは起動時に ポート0(OSによる自動割り当て) でバインドします。 決定したポート番号をJSONファイルに書き出し、検索コマンド側がそれを読み取ってアクセスする「ファイルベースのサービスディスカバリー」を実装しました。
3. SSL証明書問題の解決
Splunkからサブプロセスを起動すると、環境変数 SSL_CERT_FILE がSplunk独自のものに上書きされます。これが原因で、外部のPython環境からHugging Faceへのモデルダウンロードが失敗しました。 Managerスクリプトで os.environ.copy() し、SSL_CERT_FILE などを削除(unset)してからサブプロセスを呼ぶことで解決しました。
実装の抜粋
コマンド定義 (commands.conf)
ここでのポイントは streaming = true です。 時系列データ(timechart の結果)はすでに集計済みのため、MapReduceを行わず、サーチヘッド上で全データを処理するように指定します。(今回はこれで勘弁)
[ctsm]
filename = ctsm_command.py
chunked = true
python.version = python3
local = true
streaming = true
推論サーバー (server.py)
FastAPIでラップし、Ciscoモデル(CiscoTsmMR)を常駐させます。
# (前略)
@app.post("/predict")
async def predict(req: ForecastRequest):
# モデルによる推論実行
forecast_preds = model.forecast([input_series], horizon_len=req.horizon)
return {"forecast": forecast_preds[0]['mean'].tolist()}
if __name__ == "__main__":
# ポート0で起動し、番号をファイルに記録
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 0))
port = sock.getsockname()[1]
# ... (JSON書き出し) ...
uvicorn.run(app, host="127.0.0.1", port=port)
デモ:実際に動かしてみる
Splunkを再起動し、以下のSPLを実行します。ダミーの正弦波データを作成し、| ctsm で100ポイント先まで予測させます。
| makeresults count=100
| streamstats count
| eval _time=now() - (100-count)*3600
| eval cpu_usage = 50 + 20*sin(count/5) + random()%10
| timechart span=1h max(cpu_usage) as usage
| ctsm field=usage horizon=100
結果:
青色の実測値の周期性をモデルが理解し、赤色の予測値として綺麗に波形を描いているのが分かります。
まとめ
「Splunk Appの中にAI環境を封じ込める」というアプローチは、依存関係の管理が大変ですが、uv と Modular Input を組み合わせることで、今回のような小型モデルをある程度実用的なレベルで実装できました。
この構成なら、Ciscoのモデルに限らず、あらゆるPython製AIモデルをSplunkのカスタムコマンドとして配布可能できるのではとおもいました!
おまけ:10年以上前の「陣痛予測」と、今の「AI予測」
この記事を書いている最中、ふと15年前のことを思い出しました。
2014年頃、当時妻が妊娠中で、いよいよ出産というタイミングでのことです。 職業病でしょうか、私は病院で**「陣痛の間隔」と「継続時間」**を携帯の画面を叩くたびにひたすら記録するアプリを妻に渡し、それを当時の Splunk (v4系) に取り込んで可視化していました。(すんまへん)
「間隔が10分を切ったから、あと○時間で生まれるはず!」
Splunkのダッシュボードに描かれるグラフを見ながら、まだ見ぬ我が子が出てくる時間を予測していたのです(妻には「そんなことしてないで腰をさすって!」と怒られたかもしれませんが...笑)。
当時の限界と、CTSMの可能性
当時のSplunk標準機能(predictコマンドなどの線形回帰)では、「だんだん間隔が短くなっている」というトレンドは出せても、お産本番(Active Labor)に向けて急激に加速する非線形な変化までは正確に捉えきれませんでした。
あれから15年。 今回試した Cisco Time Series Model (CTSM) は、サイン波の周期性や文脈を「学習なし(Zero-shot)」で理解してくれました。もしあの時、このモデルがあったら、「パパの予測通りに生まれたね!」ともっと正確な予言ができていたかもしれません。
その時お腹にいた子供も、もう15歳。 技術は進化し、統計モデルから基盤モデルへと変わりましたが、「データから未来を少しだけ覗いてみたい」 というエンジニアの好奇心は、いつの時代も変わらないなと改めて感じました。
SplunkとAIの進化に乾杯!

