llm-jp-4-32b-a3b-base-NVFP4を翻訳運用で評価し、常駐translatorをやめてオンデマンドバッチへ切り替えた
llm-jp-4-32b-a3b-base-NVFP4をvLLM 0.18.0で単GPU検証し、SFT/DPO+LoRA前提から、必要時のみDagsterで翻訳バッチを起動する構成へ切り替えた実測記録。
llm-jp/llm-jp-4-32b-a3b-base の翻訳運用を前提に、SFT/DPOデータ整備とLoRA準備を進めていた。ただ、llm-jp-4-32b-a3b-base-NVFP4 を単GPUで実測すると PP/TG が想定より速く、常駐translatorロールを維持するより、必要時だけオンデマンドで翻訳バッチを流す方が全体最適だと判断した。
モデル提供元: llm-jp on Hugging Face
実行コマンド
検証時に実行した起動コマンドは以下。
podman run --rm -it \
--device nvidia.com/gpu=0 \
--ipc=host \
-e HF_HOME=/hf \
-e HF_HUB_OFFLINE=1 \
-v /mnt/data/models:/hf/hub:ro \
-p 9000:9000 \
registry.home.arpa/vllm-openai:v0.18.0-cu130 \
/hf/hub/llm-jp-4-32b-a3b-base-NVFP4 \
--host 0.0.0.0 \
--port 9000 \
--trust-remote-code \
--quantization compressed-tensors \
--served-model-name translator \
--dtype bfloat16 \
--gpu-memory-utilization 0.80 \
--max-num-seqs 8 \
--max-model-len 32678 \
--max-num-batched-tokens 1024 \
--no-enable-prefix-caching
prefix caching はあえて無効化した。翻訳用途では固定prefix再利用の旨味が薄く、まず素の挙動を測りたかったため。
起動ログ(重要部分の生ログ)
まず、起動時に読み取るべき初期条件はこの行。
(APIServer pid=1) INFO 04-14 08:08:26 [utils.py:233] non-default args: {'model_tag': '/hf/hub/llm-jp-4-32b-a3b-base-NVFP4', 'host': '0.0.0.0', 'port': 9000, 'model': '/hf/hub/llm-jp-4-32b-a3b-base-NVFP4', 'trust_remote_code': True, 'dtype': 'bfloat16', 'max_model_len': 32678, 'quantization': 'compressed-tensors', 'served_model_name': ['translator'], 'gpu_memory_utilization': 0.8, 'enable_prefix_caching': False, 'max_num_batched_tokens': 1024, 'max_num_seqs': 8}
次に、モデルロードと初期化完了までの区間。
(EngineCore pid=118) INFO 04-14 08:08:35 [gpu_model_runner.py:4481] Starting to load model /hf/hub/llm-jp-4-32b-a3b-base-NVFP4...
(EngineCore pid=118) INFO 04-14 08:08:39 [default_loader.py:384] Loading weights took 3.32 seconds
(EngineCore pid=118) INFO 04-14 08:08:39 [gpu_model_runner.py:4566] Model loading took 18.23 GiB memory and 3.843664 seconds
(EngineCore pid=118) INFO 04-14 08:08:52 [monitor.py:48] torch.compile took 12.35 s in total
(EngineCore pid=118) INFO 04-14 08:08:52 [monitor.py:76] Initial profiling/warmup run took 0.51 s
(EngineCore pid=118) INFO 04-14 08:09:29 [gpu_worker.py:456] Available KV cache memory: 56.62 GiB
(EngineCore pid=118) INFO 04-14 08:09:29 [kv_cache_utils.py:1316] GPU KV cache size: 927,600 tokens
(EngineCore pid=118) INFO 04-14 08:09:29 [kv_cache_utils.py:1321] Maximum concurrency for 32,678 tokens per request: 28.38x
単発・一括の実測
単発リクエスト:
(APIServer pid=1) INFO 04-14 08:11:32 [loggers.py:259] Engine 000: Avg prompt throughput: 12.5 tokens/s, Avg generation throughput: 25.6 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 0.0%
一括 (seq=8, ctx=32768) の代表ログ:
(APIServer pid=1) INFO 04-14 07:34:20 [loggers.py:259] Engine 000: Avg prompt throughput: 353.8 tokens/s, Avg generation throughput: 157.0 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.4%, Prefix cache hit rate: 0.0%
(APIServer pid=1) INFO: 10.0.2.100:41472 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=1) INFO: 10.0.2.100:41484 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=1) INFO: 10.0.2.100:36782 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=1) INFO: 10.0.2.100:36788 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=1) INFO: 10.0.2.100:36790 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=1) INFO 04-14 07:34:30 [loggers.py:259] Engine 000: Avg prompt throughput: 2768.4 tokens/s, Avg generation throughput: 159.5 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 1.0%, Prefix cache hit rate: 0.0%
(APIServer pid=1) INFO 04-14 07:34:40 [loggers.py:259] Engine 000: Avg prompt throughput: 276.4 tokens/s, Avg generation throughput: 182.2 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.5%, Prefix cache hit rate: 0.0%
(APIServer pid=1) INFO 04-14 07:34:50 [loggers.py:259] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 182.1 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.7%, Prefix cache hit rate: 0.0%
(APIServer pid=1) INFO 04-14 07:35:00 [loggers.py:259] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 179.2 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.9%, Prefix cache hit rate: 0.0%
この検証では概ね次のレンジだった。
- Prompt throughput: 約
350-1360 tok/s(観測上は2768.4 tok/sの瞬間ピークあり) - Generation throughput: 約
152-183 tok/s - Prefix cache hit rate: 常時
0.0%(--no-enable-prefix-cachingと整合)
翻訳用途としては十分に実用域。max_num_seqs=8 は保守的で、短〜中入力中心ならまだ余地がある。
decode速度の漸減は異常か
長い生成中は decode が徐々に落ちる。
183.2 -> 180.5 -> 178.2 -> 176.4 -> 173.3 -> 170.0 -> 169.3 -> 167.9
169.7 -> 168.9 -> 167.2 -> 165.5 -> 163.7 -> 160.9 -> 159.4 -> 158.0 -> 156.3 -> 154.7 -> 152.2
方針転換の根拠(設計)
速度と安定性の実測を踏まえ、設計は次へ切り替えた。
- 自作ハーネスから
translatorロールを削除 - Dagsterパイプラインのトリガーで翻訳バッチを起動
- 常時推論用VRAMを優先確保
もともとは翻訳専用LoRAを前提にして SFT/DPO データ整備を進めていたが、実測の PP/TG が十分速く、常駐翻訳ロール維持の費用対効果が低かった。リアルタイムで翻訳して再学習してみたいな自動化がないなら、すぐに翻訳が必要なこともないし、必要なときにバッチで処理してもこの速さなら大差ない。むしろ推論中のノード全体のリソース効率が良い。
次の実装案
llm-jp-4-32b-a3b-base を翻訳専用に寄せる作業は、想定より手間がかかる可能性がある。なので次の検証は、まず thinking:low を使った再検証をしたいとおもう。
thinking の reasoning: low|middle|high を用途別に切り替えられる前提で、まずは次の2段を試すつもりだ。
gemmatranslate4b-itで堅い日本語へ一次変換llm-jp-32b-a3b-thinking:lowで流暢化・読み味調整
thinking:low ,LoRAで定着が難しい場合は、gemmatranslate-4b-it と組み合わせた2段構成をどこまで実運用に寄せられるか肌感覚だけは掴んでおきたい。
まとめ
llm-jp-4-32b-a3b は、今回の単GPUでのnvfp4で動作検証が確認できた。ちなみに
--tensor-parallel-size 2で試したがIntermediate size padding for w1 and w3 ... not currently supportedエラーになり、--moe-backend cutlassなどauto以外を指定してみたがだめだった。instance x 2で動かせば回避できるし、今回は触りで時間を掛けたくなかったので深入りせず。ひとまず結論は、常駐translatorよりオンデマンド翻訳バッチへ切り替える方針もきまったので有意義でした。
