Instana + vLLM + Fine-Tuned Llama3 での監視・トレース検証

## **はじめに**
本記事では、Fine-Tuned した Llama 3.2-1B モデル ([Llama 3.2-1B を Unsloth + vLLM でファインチューニング & 高速推論](https://qiita.com/jackyxu/items/eca09b1f75e70db9d6ef)で[10万件整理した日本ニュースデータ](https://huggingface.co/datasets/kajuma/CC-news-2024-July-October-cleaned)にてTrainingしてモデル)を vLLM でデプロイし、Instana + OpenTelemetry で監視・トレースを行う手順を解説 します。

## vLLM とは？
vLLM は、LLM を効率的に推論 (inference) するための最適化された推論エンジンです。特に 高速なトークン生成と低レイテンシー に優れており、WatsonX AI のような大規模モデルを効率的に利用するためのプラットフォームとして活用できます。

## **検証環境**
| **構成** | **バージョン / スペック** |
|-----------|------------------|
| OS | Ubuntu 20.04 |
| GPU | RTX 4090 (16GB VRAM) |
| Python | 3.10.12 |
| vLLM | 0.7.1 |
| Instana Agent | 最新版 |
| Fine-Tuned Model | `merged_llama3_2_1b` (カスタム Llama3) |

**1. Instana Agent のセットアップ**
まずは、**Instana Agent** をインストールし、**OpenTelemetry のポート (4317) を有効化** します。

**1.1 Instana Agent のインストール**
[Linux環境のインストールドキュメント](https://www.ibm.com/docs/ja/instana-observability/current?topic=linux-installing-agent)をご参照。

**1.2 OpenTelemetry の有効化**
1.2.1 [instana_installation_dir]/etc/instana/configuration.yaml
```yaml
com.instana.plugin.opentelemetry:
  enabled: true
```

**1.2.2 Instana Agentを再起動**
```bash
sudo systemctl restart instana-agent.service
netstat -ano | grep 4317
```
✅ **`0.0.0.0:4317` が表示されれば成功！**


## **2. Python 環境のセットアップ**
vLLM と OpenTelemetry を動作させるために、**仮想環境を作成し、必要なライブラリをインストール** します。

```bash
# Python の仮想環境を作成
python3 -m venv vllm_v0.7.1
source vllm_v0.7.1/bin/activate

# 必要なパッケージをインストール
pip install vllm fastapi uvicorn
pip install 'opentelemetry-sdk>=1.30.0' \
            'opentelemetry-api>=1.30.0' \
            'opentelemetry-exporter-otlp>=1.30.0' \
            'opentelemetry-instrumentation-fastapi>=0.51b0' \
            'opentelemetry-instrumentation-asgi>=0.51b0' \
            'opentelemetry-semantic-conventions-ai>=0.5.0'
```

## **3. OpenTelemetry関連の環境変数設定**
今回の場合に、Agent経由でOpentelemetryで収集した情報をInstanaのSaaS Backendに送信します。
```bash
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4317"
export OTEL_SERVICE_NAME="vllmservicejacky"
export OTEL_EXPORTER_OTLP_INSECURE=true
```

## **4. vLLM + Fine-Tuned Llama3 の起動**
PythonコードにてvLLM を API サーバーとして起動し、**Fine-Tuned Llama3 モデル (`merged_llama3_2_1b`)** をロードします。
ファイル：trainingtest/api_vllm_otel.py
```python
import os
from fastapi import FastAPI, Request
from pydantic import BaseModel
from vllm import LLM, SamplingParams
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

# ✅ OpenTelemetry の設定
trace.set_tracer_provider(TracerProvider())
trace_exporter = OTLPSpanExporter(endpoint=os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "http://localhost:4317"))
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(trace_exporter))

# ✅ FastAPI アプリの作成
app = FastAPI()

# ✅ FastAPI に OpenTelemetry のトレースを適用
FastAPIInstrumentor.instrument_app(app)

# ✅ マージ済みモデルのパス
model_path = "./trainingtest/models/merged_llama3_2_1b"

# ✅ vLLMで統合済みモデルをロードし、OpenTelemetry のエンドポイントを指定
llm = LLM(model=model_path, dtype="float16", otlp_traces_endpoint=os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"))

# ✅ サンプリング設定（繰り返し防止を強化）
class InputText(BaseModel):
    prompt: str
    max_tokens: int = 200
    temperature: float = 1.0  # 🔼 低すぎると繰り返しやすい
    top_p: float = 0.9  # 🔼 確率の高い候補を広く取る
    top_k: int = 50
    repetition_penalty: float = 1.2  # 🔥 追加！繰り返しを防ぐ

@app.post("/generate/")
async def generate_text(input_text: InputText, request: Request):
    """
    統合済み Llama3 モデルを使用してテキストを生成する API
    """
    tracer = trace.get_tracer(__name__)
    with tracer.start_as_current_span("generate_text"):
        sampling_params = SamplingParams(
            max_tokens=input_text.max_tokens,
            temperature=input_text.temperature,
            top_p=input_text.top_p,
            top_k=input_text.top_k,
            repetition_penalty=input_text.repetition_penalty  # 🔥 追加！
        )

        outputs = llm.generate(input_text.prompt, sampling_params)
        generated_text = outputs[0].outputs[0].text  # 最初の応答のみ取得

        return {"generated_text": generated_text}

# ✅ サーバー起動（スクリプト実行時）
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
```

```bash
python trainingtest/api_vllm_otel.py
```
```bash
....
INFO 02-25 14:12:43 model_runner.py:1115] Loading model weights took 2.3185 GB
INFO 02-25 14:12:44 worker.py:267] Memory profiling takes 0.47 seconds
INFO 02-25 14:12:44 worker.py:267] the current vLLM instance can use total_gpu_memory (15.99GiB) x gpu_memory_utilization (0.90) = 14.39GiB
INFO 02-25 14:12:44 worker.py:267] model weights take 2.32GiB; non_torch_memory takes 0.05GiB; PyTorch activation peak memory takes 1.18GiB; the rest of the memory reserved for KV Cache is 10.84GiB.
INFO 02-25 14:12:44 executor_base.py:111] # cuda blocks: 22207, # CPU blocks: 8192
INFO 02-25 14:12:44 executor_base.py:116] Maximum concurrency for 131072 tokens per request: 2.71x
INFO 02-25 14:12:45 model_runner.py:1434] Capturing cudagraphs for decoding. This may lead to unexpected consequences if the model is not static. To run the model in eager mode, set 'enforce_eager=True' or use '--enforce-eager' in the CLI. If out-of-memory error occurs during cudagraph capture, consider decreasing gpu_memory_utilization or switching to eager mode. You can also reduce the max_num_seqs as needed to decrease memory usage.
Capturing CUDA graph shapes: 100%|███████████| 35/35 [00:09<00:00,  3.87it/s]
INFO 02-25 14:12:54 model_runner.py:1562] Graph capturing finished in 9 secs, took 0.14 GiB
INFO 02-25 14:12:54 llm_engine.py:436] init engine (profile, create kv cache, warmup model) took 10.36 seconds
Overriding of current TracerProvider is not allowed
INFO:     Started server process [1226578]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
```

## **5. Instana の UI にログインし、アプリケーション画面にあるサービス一覧の中に、 `vllmservicejacky` が検出されたことを確認します。**

## 6. 上記の検出されたサービスで新規アプリケーション・パースペクティブ（例：llama3.2_vllmservicejacky)を作成します。


## 7. Curlコマンドでのリクエストテスト
Curlコマンドから先ほど起動したAPIサービスにあるLLMモデルを呼び出し、OpenTelemetry でトレースを記録します。
7.1 Prompt=`"2020年以降の流行語大賞を振り返ると、上位に「アーカイブ」が選ばれた。いったいなぜかと。"`でのテスト
```bash
curl -X POST "http://127.0.0.1:8000/generate/" \
     -H "Content-Type: application/json" \
     -d '{"prompt": "2020年以降の流行語大賞を振り返ると、上位に「アーカイブ」が選ばれた。いったいなぜかと。", "max_tokens": 512, "temperature": 1.0, "top_p": 0.9, "top_k": 50, "repetition_penalty": 1.2}'
```
```bash
{"generated_text":"歴史学者・東洋大学教授で日本放送連盟理事長（現在）は、「世界ではお互いに近くも遅めやても見ている」「伝統的な記憶にとって重要だが、今は 『新しい情報』との間に位置付けられている」と話す。\n\nデジタル時代でも経済発 展継続するためには？\nデモクラシー化へ移るオリンピック体制\n\n## 「世俗的」の表現代用語として初々しく取り込まれましたね。\n\nこれまで今世紀に入っても時に はじまり始めたのでそのときにこの言葉が起きて以来、日本語にもたらされたという ことだと思います。「本来地政学者だけでなく人類社会全体において重要なものがあ ることが理解されなければ」という意味ですから、その日付より先だったら私たちが いかに理解できたのかといえばそれほど分かりません。\nしかし、私自身は明治期に なって20年にわたり地政学者の研究活動をすることにしたのがキッカケであり、それ だけあれば国民意識を持っている人はしっぺいておりますよ。その中であれかしらの 例のように言いたかったのは当時にあり得ないことを強調して、平和実務部門での知 名度の上昇になるように動向性をつけたい思いがあったと思う。\n\nまた、いま回し になによろこびが出ていなかったり、反映されるのに不便であるようなことは正確に 触れるために書きたくなっていけたかなろうとは考えてしまっていたのもいいのでは ないでしょう。この種の変革の形になった元気があるわけなのですが、一番うれしか った事については「こういう時間帯に出会えた時の感覚があり、どのようなことで自 分が活躍できるようになりそうだと感じられた瞬間」を捉えていたんです！\nもう一 つは、物質文化革命が終結していることも重なりの原因となされているのですが、そ ういう頃には英語ではなくニュース系雑誌の方を見る場面となったりと、本格的に受 ける権利を手にする自由さが広まったことに関わり、このように議論を繰り出してい たのではありません。そしてそんな時の中で自分の役割を生み出す戦略策を作成しよ うとした際につながったのは、政府関係員のみならず法界の人達によって多くの法律 の問題解決にかかる力"}(vllm_v0.7.1) 
```

7.2 Prompt=`"2024年のTopニュースは？"`でのテスト
```bash
curl -X POST "http://127.0.0.1:8000/generate/"  \
     -H "Content-Type: application/json"  \
     -d '{"prompt": "2024年のTopニュースは？", "max_tokens": 200, "temperature": 1, "top_p": 0.95, "top_k": 50,  "repetition_penalty": 1.2}'
```
```bash
{"generated_text":"　日曜・午後からあすまで（通算44記事）\nお手上げの10代が「テクスショー」に現れたら・・・\n\n# 日本代表も含め全選手、オリンピックで新型 コロナ感染者！「大変な状況でした」\n\n日本代表も含め全選手、新型コロー難と発 表＝東京五輪／大会開催記念動画解説者インタビュー連載第6回～()\n\nこの夏のパリ五輪では、日本人男性チームが主将を務めた。\nイメージ写真を参照してください。\n\n## 「体調にかわりゃいらない」\n上野麻由さんからのメッセージとして、明かさ れたのは今月初日の5日に行われたトライアウト戦に向けて行っているものだった。「早速マウントするぞ」というコメントが出るというが、マルクトールにも同様"}(vllm_v0.7.1)
```

### **8. Instana の UI で該当サービスの可視化の確認**
**8.1. アプリケーション > llama3.2_vllmservicejacky にアクセス**
![スクリーンショット 2025-02-25 162138.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629252/58b1f9c0-8048-4cff-a178-208721fd625a.png)


**8.2. 分析 > アプリケーション > 呼び出し に確認**
![スクリーンショット 2025-02-25 162206.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629252/be943155-74c8-4b55-8c9b-72a0b6f60567.png)


**8.3. 呼び出しとトレースの詳細を確認**
![スクリーンショット 2025-02-25 162228.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629252/32563897-218f-455f-ac3c-6d61d8d5e789.png)



## **まとめ**
✅ **Fine-Tuned Llama3 を vLLM でデプロイ**  
✅ **Instana + OpenTelemetry で LLM のトレースを可視化**  

🚀 **これで Llama3 の監視とトレースが可能になりました！**