日本語タスクを評価する推論基盤 マルチ LLM 対応の NVIDIA NIM
本ブログでは、下記の記事で作成した合成データを用いて Supervised Fine-Tuning (SFT) したモデルの日本語性能を評価します。本記事では Supervised Fine-Tuning を便宜上 SFT の略称で記載します。
- Nemotron-Personas-Japan を用いた NVIDIA NeMo Data Designer による合成データ生成
- NVIDIA NeMo RL を用いた合成データによる Supervised Fine-Tuning (SFT)
本評価では、推論基盤としてマルチ LLM 対応のNVIDIA NIM を使用しました。
マルチ LLM 対応の NVIDIA NIM は、
- OpenAI 互換 API を標準提供
- 幅広い LLM を同一の推論マイクロサービス形態でデプロイ可能
- 評価コード側を変えずにモデル差し替えが可能
という特徴を持ちます。
同一の推論条件、API、運用形態のもとで、異なるモデルを公平に評価できる点が特徴です。これにより、 推論基盤差による評価ノイズを最小化した状態で、 SFT の効果そのものを観測できます。
本記事では、以下について解説します。
- マルチ LLM 対応の NVIDIA NIM を用いた SFT 済みモデルのデプロイ方法
- llm-jp-eval を用いた日本語常識推論タスクの評価手法
- Seed あり / なし合成データ SFT の効果比較
検証環境
本記事で検証に使用した環境は下記です。
ハードウェア動作環境
- DGX-A100
ソフトウェア環境
- llm-jp/llm-jp-eval at v2.1.2
- 2026.01.06 時点の main ブランチのコードを使用
NVIDIA-NeMo/RL: Scalable toolkit for efficient model reinforcement - マルチ LLM 対応の NVIDIA NIM | NVIDIA NGC
JCommonsenseQA の比較評価
モデルの変換処理
「NeMo RL を用いた合成データによる Supervised Fine-Tuning (SFT)」の記事で指定したディレクトリにある checkpoint を使います。
NeMo RL で出力されるチェックポイントは Hugging Face の Safetensors フォーマットでないため変換処理が必要です。convert_dcp_to_hf.py で HF checkpoint に変換します。
uv run python examples/converters/convert_dcp_to_hf.py \
--config="<path/to/checkpoints/config.yaml>" \
--dcp-ckpt-path="<path/to/checkpoints/step*/policy/weights>" \
--hf-ckpt-path="$HF_CKPT_PATH"
2022.01.06 時点で直接、Safetensors フォーマットに変換するコードがないので下記コードで HF checkpoint から Safetensors フォーマットに変換する処理を行います。
import argparse
import shutil
import os
from transformers import AutoModelForCausalLM
from huggingface_hub import hf_hub_download
def copy_files_from_hub(original_model_name: str, hf_ckpt_path: str):
"""必要なファイルを HF Hub からコピー"""
files_to_copy = [
"modeling_nemotron_h.py",
"configuration_nemotron_h.py",
"special_tokens_map.json",
"tokenizer_config.json",
"tokenizer.json",
]
os.makedirs(hf_ckpt_path, exist_ok=True)
for filename in files_to_copy:
try:
src_file = hf_hub_download(
repo_id=original_model_name,
filename=filename,
)
dst_file = os.path.join(hf_ckpt_path, filename)
shutil.copy(src_file, dst_file)
print(f"[OK] Copied {filename}")
except Exception as e:
print(f"[WARN] Failed to copy {filename}: {e}")
def convert_to_safetensors(hf_ckpt_path: str):
"""HF checkpoint → safetensors に変換"""
print(f"\n=== Loading model from: {hf_ckpt_path} ===")
model = AutoModelForCausalLM.from_pretrained(hf_ckpt_path, trust_remote_code=True)
output_dir = hf_ckpt_path + "_safetensors"
print(f"=== Saving safetensors to: {output_dir} ===")
model.save_pretrained(output_dir, safe_serialization=True)
# tokenizer / tokens を safetensors 側にもコピー
files_to_copy = [
"special_tokens_map.json",
"tokenizer_config.json",
"tokenizer.json",
]
for filename in files_to_copy:
src_file = os.path.join(hf_ckpt_path, filename)
dst_file = os.path.join(output_dir, filename)
shutil.copy(src_file, dst_file)
print(f"[OK] Copied {filename} to safetensors directory")
print("\n=== Conversion completed ===")
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--hf-ckpt-path",
type=str,
required=True,
help="Path to HF checkpoint directory"
)
parser.add_argument(
"--original-model-name",
type=str,
default="nvidia/NVIDIA-Nemotron-Nano-9B-v2",
help="Original model name used for training"
)
args = parser.parse_args()
hf_ckpt_path = args.hf_ckpt_path
original_model_name = args.original_model_name
print(f"Using HF checkpoint path: {hf_ckpt_path}")
print(f"Original model name: {original_model_name}")
copy_files_from_hub(original_model_name, hf_ckpt_path)
convert_to_safetensors(hf_ckpt_path)
if __name__ == "__main__":
main()
下記コマンドで Safetensors フォーマットに変更します。
uv run python examples/convert_hf_safetensors.py --hf-ckpt-path="$HF_CKPT_PATH"
マルチ LLM 対応の NVIDIA NIM でのデプロイ
Example Deployment を参考に {HuggingFace Safetensors path} に先程変換した Safetensors の path を設定して マルチ LLM 対応の NVIDIA NIM でデプロイします。
export CONTAINER_NAME=LLM-NIM
export Repository=nim/nvidia/llm-nim
export TAG=1.15.0
export IMG_NAME="nvcr.io/$Repository:$TAG"
export LOCAL_MODEL_DIR={HuggingFace Safetensors path}
export NIM_MODEL_NAME=$LOCAL_MODEL_DIR
export NIM_SERVED_MODEL_NAME=custom_nemotron_nano_v2_9b
# Choose a path on your system to cache the downloaded models
export LOCAL_NIM_CACHE=~/.cache/nim
mkdir -p "$LOCAL_NIM_CACHE"
# Add write permissions to the NIM cache for downloading model assets
chmod -R a+w "$LOCAL_NIM_CACHE"
# Start the LLM NIM
docker run -it --rm --name=$CONTAINER_NAME \
--gpus '"device=0"' \
--shm-size=124GB \
-e NIM_MODEL_NAME="/opt/models/local_model" \
-e NIM_SERVED_MODEL_NAME=$NIM_SERVED_MODEL_NAME \
-v "$LOCAL_MODEL_DIR:/opt/models/local_model" \
-v "$LOCAL_NIM_CACHE:/opt/nim/.cache" \
-e NIM_FORCE_TRUST_REMOTE_CODE=1 \
-e NIM_MANIFEST_ALLOW_UNSAFE=1 \
-u $(id -u) \
-e NIM_MAX_NUM_SEQS=256 \
-e NIM_MAX_BATCH_SIZE=16 \
-e NIM_MAX_MODEL_LEN=4096 \
-e NIM_KVCACHE_PERCENT=0.5 \
-p 8000:8000 \
$IMG_NAME
JCommonsenseQA とは
JCommonsenseQA は、日本語での常識推論能力を評価する多肢選択式 QA タスクです。英語版 CommonsenseQA をベースに、日本語話者向けに構築されており、JGLUE ベンチマークの一部としても利用されています。
特徴は以下の通りです。
- 日本語の一般常識、語義、手順、制度理解を問う
- 5 択の常識推論タスクのため推論の曖昧さが入りにくく、モデル比較に適している
そのため、合成データ生成の品質評価対象として扱いやすいという理由から、本記事では JCommonsenseQA を対象タスクとして選択しています。
llm-jp-eval での評価
JCommonsenseQA を評価するために llm-jp-eval の v_2.1.2 を使用しました。llm-jp-eval は、日本語 LLM の性能を統一フォーマットおよび統一指標で評価するための OSS フレームワークです。
主な特徴は下記です
- 日本語タスク (JGLUE 系、JCommonsenseQA など) に対応
- few-shot / zero-shot の統一的な評価設定
- Exact Match を中心とした再現性の高い指標設計
- vLLM / OpenAI 互換 API を利用可能
推論実行方法に沿ってデータセットを準備します。
下記のような datasets_jcommonsense.yaml を準備して eval_configs/datasets_jcommonsense.yaml に置きます。JCommonsenseQA のみを対象として評価するように設定しています。
datasets:
- jcommonsenseqa
# - hle
categories:
CR:
description: "nemotron"
default_metric: exact_match
metrics: {}
datasets:
- jcommonsenseqa
dataset_info_overrides: {}
下記のような config_sft_jcommonsense.yaml を準備して configs/config_sft_jcommonsense.yaml に置きます。config_template.yaml をベースに変更しています。
マルチ LLM 対応の NVIDIA NIM は OpenAI フォーマットに準拠しているので vLLM の設定で動作可能なため、vLLM の設定で動作させます。
`custom_prompt_template` を修正して think を off で動作するように設定します。
/no_think を指定することで、推論時に思考トークン (Chain-of-Thought) を出力しない設定としています。
run_name: null # 最優先で適用されるrun_name。指定しない場合は`wandb.run_name`またはモデル名に実行日時を結合したデフォルト値が用いられる。
output_dir: 'local_files'
eval_dataset_config_path: './eval_configs/datasets_jcommonsense.yaml' # 評価データセットの設定ファイルのパス
include_non_commercial: false # true の場合、商用利用不可のデータセットも含めて評価します。
max_num_samples: -1 # 評価データセットのサンプル数の上限。デフォルトは10件。-1の場合は全件評価します。
exporters:
local:
export_output_table: true # 出力結果をテーブル形式で保存するかどうか
output_top_n: null # 出力結果の上位何件を保存するか
# HTTP Requestなどによる同期的推論を行う場合の設定
online_inference_config:
provider: vllm-openai
max_concurrent: 200
hostname: localhost:8000
model_name: "custom_nemotron_nano_v2_9b"
generation_config:
temperature: 0.0
custom_prompt_template: |
/no_think
{%- if dataset_instruction -%}
### 指示
{{ dataset_instruction.strip() }}
{% endif -%}
{%- if answer_pattern -%}
### 回答形式
{{ answer_pattern.strip() }}
{% endif -%}
### 入力:
{{ input.strip() }}
default_answer_extract_pattern: "(?s)^(.*?)(?=\\n\\n|\\Z)"
output_length_delta: 0
下記コマンドで JCommonsenseQA の評価処理を実行します。
uv run scripts/evaluate_llm.py eval --config configs/config_sft_jcommonsense.yaml
合成データ適用前の基礎性能比較
評価対象モデルの JCommonsenseQA の評価結果は下記です。
| 評価対象モデル | 評価結果 |
| ベース モデル: nvidia/nvidia-nemotron-nano-9b-v2 (/no_think を設定して think を off にして検証しています。) | 0.8686 |
| 合成データ生成モデル: openai/gpt-oss-120b (デフォルト設定で検証しています。) | 0.9589 |
誤答分析
nvidia/nvidia-nemotron-nano-9b-v2 では特定のジャンルで誤答が発生しました。一例として“地理・生活圏常識” で誤答が発生したので、その内容をピックアップします。
- 質問: 砂丘のある県の町は?
- 選択肢 (抜粋)
- 鳥取県
- 鳥取市 (正解)
- nvidia/nvidia-nemotron-nano-9b-v2 の予測
- 鳥取県
- 選択肢 (抜粋)
都道府県レベル (prefecture) で止まってしまうミスが発生しています。
性能評価
nvidia/nvidia-nemotron-nano-9b-v2 は/no_think を設定して think を off にして検証しています。
| モデル | 学習 / 役割 | JCommonsenseQA Exact Match |
| Nemotron-Nano-9B-v2 (Base) | ベース モデル | 0.8686 |
| GPT-OSS-120B | 合成データ生成 (教師) | 0.9588 |
| Nemotron-Nano-9B-v2 Seed なし SFT | 合成データ (約 8,000 件) SFT | 0.8766 |
| Nemotron-Nano-9B-v2 Seed あり SFT | Nemotron-Personas-Japan Seed 使用した合成データ (約 8,000 件) SFT | 0.8892 |
ベース モデルの初期性能が高いため、改善幅は限定的でしたが、合成データ SFT による一貫した性能向上が確認できました。
Seed なし、ありの合成データでも性能が改善しました。
Seed あり / なしの比較では、モデル構造、学習ステップ数、評価設定はすべて同一条件としています。
Seed ありでは“地理・生活圏常識” の誤答が改善されました。Seed としていれた居住地 (都道府県) もしくは生活圏 (地方) の情報が効いた可能性があります。
- 質問: 砂丘のある県の町は?
- nvidia/nvidia-nemotron-nano-9b-v2 Seed あり SFT の予測
- 鳥取市
- nvidia/nvidia-nemotron-nano-9b-v2 Seed あり SFT の予測
まとめ
本記事では、下記を記述しました。
- マルチ LLM 対応の NVIDIA NIM での実行方法
- llm-jp-eval を用いた評価方法
- 評価結果の比較
一連の記事で Seed ありの合成データによるモデルの改善の方法を示したので、参考になれば幸いです。