llm-jp-4-33b-thinking-nvfp4

llm-jp/llm-jp-4-33b-thinkingllm-compressorNVFP4 (W4A4) に量子化したモデルです。

bf16で約62GiBあったモデルが 20.1GiB になり、RTX 5090 (32GB) 1枚で動作します。 RTX 5090・--max-model-len 16384 の実測で、KVキャッシュに 6.09GiB(24,944トークン分)を確保できました。

このモデルはHarmony応答形式を使う thinking モデルです。vLLMで正しく動かすには --trust-remote-code専用のreasoning parserが必要です。 詳細は「vLLMでの使い方」を必ずお読みください。

量子化仕様

項目 内容
量子化形式 NVFP4 (nvfp4-pack-quantized)
重み FP4 (E2M1) / group size 16 / tensor_group / スケールはFP8 (E4M3)
活性化 FP4 (E2M1) / group size 16 / 実行時に動的量子化、グローバルスケールはキャリブレーション済み
量子化対象 全ての Linear
除外 lm_head(bf16のまま)
アルゴリズム QuantizationModifier(RTN + 活性化スケールのキャリブレーション)
llm-compressor 0.13.0
compressed-tensors 0.18.0

embed_tokenslm_head はbf16のまま残しています(それぞれ約2.0GiB)。

動作環境

NVFP4は Blackwell世代 (sm_120以降) のネイティブFP4演算を前提とします。

検証GPU NVIDIA GeForce RTX 5090 (32GB, sm_120)
NVIDIA driver 580.159.03 / CUDA 13.0
vLLM 0.27.1
カーネル FlashInfer b12x (SM120向けネイティブNVFP4 GEMM)

sm_100系 (B100/B200) でも動作します。sm_120未満のGPUではvLLMがMarlinの 重みのみ量子化 (W4A16) にフォールバックするため、動作はしますが本来の速度は出ません。

起動時のログに次の行が出ていれば、ネイティブFP4演算が使われています。

Using FlashInferCutlassNvFp4LinearKernel for NVFP4 GEMM

RTX 5090での実測値(--max-model-len 16384、単一リクエスト):

項目 実測
モデルサイズ 20.1 GiB
重みロード時間 2.4秒
KVキャッシュ 6.09 GiB (24,944トークン)
生成速度 約66 tok/s

長いコンテキストの同時リクエストを増やしたい場合は --kv-cache-dtype fp8 を付けると KVキャッシュの容量がおよそ倍になります。

vLLMでの使い方

1. reasoning parser を用意する

llm-jp-4はOpenAI Harmony形式で応答しますが、トークナイザがHarmony公式実装と異なるため、 専用のパーサーが必要です。本リポジトリの vllm_plugin/llm-jp-4-cookbook 由来のファイルを同梱しています。

vllm_plugin/
├── llmjp4_harmony.py           # Harmonyトークン列のパーサー
├── llmjp4_reasoning_parser.py  # vLLM用 reasoning parser (`llmjp4` として登録)
└── example_cli.py              # パーサーを登録してから vllm CLI を起動するラッパー

2. サーバーを起動する

huggingface-cli download Holy-fox/llm-jp-4-33b-thinking-nvfp4 \
  --local-dir ./llm-jp-4-33b-thinking-nvfp4

cd ./llm-jp-4-33b-thinking-nvfp4

PYTHONPATH=vllm_plugin python3 vllm_plugin/example_cli.py serve . \
  --served-model-name llm-jp-4-33b-thinking \
  --reasoning-parser llmjp4 \
  --trust-remote-code \
  --enable-auto-tool-choice --tool-call-parser hermes \
  --max-model-len 16384 \
  --gpu-memory-utilization 0.92 \
  --host 0.0.0.0 --port 8000

--enable-auto-tool-choice はOpen WebUI等への対応用です。理由は 「Open WebUI などから使う場合」を参照してください。)

Dockerで動かす場合:

docker run --rm --gpus all -p 8000:8000 --ipc=host --shm-size=16g \
  -e PYTHONPATH=/model/vllm_plugin \
  -v "$PWD:/model:ro" \
  --entrypoint python3 \
  vllm/vllm-openai:v0.27.1 \
    /model/vllm_plugin/example_cli.py serve /model \
    --served-model-name llm-jp-4-33b-thinking \
    --reasoning-parser llmjp4 \
    --trust-remote-code \
    --enable-auto-tool-choice --tool-call-parser hermes \
    --max-model-len 16384 \
    --gpu-memory-utilization 0.92 \
    --host 0.0.0.0 --port 8000

--reasoning-parser llmjp4 を付けずに素の vllm serve を使うと、 <|channel|>analysis などの制御トークンがそのまま応答本文に混ざります。

3. リクエストを送る

必ず stream: true を使ってください。 現在のvLLMのインターフェース上の制約で、非ストリーミングでは思考内容(reasoning)と 最終回答(content)を分離できません。これはcookbookにも記載されている既知の制限です。

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llm-jp-4-33b-thinking",
    "messages": [{"role": "user", "content": "二次方程式の解の公式を導出して下さい。"}],
    "stream": true,
    "max_tokens": 4096,
    "temperature": 0.7,
    "top_p": 0.9
  }'

Python (OpenAIクライアント):

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

stream = client.chat.completions.create(
    model="llm-jp-4-33b-thinking",
    messages=[{"role": "user", "content": "日本の四季について簡潔に説明してください。"}],
    stream=True,
    max_tokens=4096,
    temperature=0.7,
    top_p=0.9,
)

reasoning, content = [], []
for chunk in stream:
    delta = chunk.choices[0].delta
    if getattr(delta, "reasoning", None):
        reasoning.append(delta.reasoning)
    if delta.content:
        content.append(delta.content)

print("--- 思考過程 (analysis) ---")
print("".join(reasoning))
print("\n--- 最終回答 (final) ---")
print("".join(content))

4. Open WebUI などから使う場合

Open WebUI で Function Calling を Native にしていると tool_choice: "auto" が送られ、 vLLM が次のエラーを返します。

"auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set

サーバー起動時に以下を追加すればエラーは解消し、通常のチャットは問題なく動作します (思考チャンネルの分離にも影響しません)。

--enable-auto-tool-choice --tool-call-parser hermes

これはエラーを回避するための措置で、ツール呼び出し自体は機能しません。 llm-jp-4はHarmonyのcommentaryチャンネルでツールを呼びますが、 vLLM 0.27.1に同梱のツールパーサーはどれもこの形式を解釈できません (gpt-oss用の openai パーサーは実体のないスタブです)。 このフラグを付けた状態でツールを渡すと、呼び出しが生のJSONテキストとして本文に出力されます。 ツールを実際に使いたい場合は、Open WebUI側の Function Calling を Default (プロンプトベース)にしてください。

5. 推論の深さ (reasoning effort) を変える

チャットテンプレートは reasoning_effort を受け取ります(low / medium / high、既定は medium)。 OpenAI互換APIからは chat_template_kwargs で指定します。

{
  "model": "llm-jp-4-33b-thinking",
  "messages": [{"role": "user", "content": "..."}],
  "chat_template_kwargs": {"reasoning_effort": "high"},
  "stream": true
}

オフライン推論 (vLLMのPython API)

from vllm import LLM, SamplingParams
import sys
sys.path.insert(0, "vllm_plugin")
from llmjp4_harmony import HarmonyMessageParser

llm = LLM(model=".", trust_remote_code=True, max_model_len=16384)
tokenizer = llm.get_tokenizer()

prompt = tokenizer.apply_chat_template(
    [{"role": "user", "content": "日本語で自己紹介してください。"}],
    tokenize=False,
    add_generation_prompt=True,
    reasoning_effort="medium",
)

out = llm.generate([prompt], SamplingParams(max_tokens=2048, temperature=0.7, top_p=0.9))
token_ids = out[0].outputs[0].token_ids

# NOTE: out[0].outputs[0].text は使わないでください(空白の扱いが崩れます)。
#       必ずトークナイザ側の decode を通してください。
parser = HarmonyMessageParser(tokenizer)
for msg in parser.iter_messages(token_ids):
    if msg.channel and msg.content:
        channel = tokenizer.decode(msg.channel.token_ids)
        text = tokenizer.decode(msg.content.token_ids)
        print(f"[{channel}] {text}")

llm-jp-4を扱う上での注意点

  • --trust-remote-code は必須です。 本モデルは llmjp4_tokenizer.pyLlamaTokenizerFast の サブクラス)を同梱しています。これはHarmonyの制御トークンとテキストトークンを分けてデコードし、 SentencePieceの空白まわりの既知の問題 (1, 2) を回避するためのものです。
  • 生の出力テキストをそのまま使わないでください。 上記の理由から、必ず同梱トークナイザの decode を通してください。
  • eos_token<|endoftext|> ではなく <|return|> (id=2) です。
  • openai-harmony ライブラリでは本モデルをトークナイズできません(語彙が異なります)。

キャリブレーション

キャリブレーションには llm-jp/llm-jp-4-33b-thinking-dpo-datachosen(選択された)側 のみを使用し、2,046サンプルを抽出しました。

思考過程(CoT)込みのHarmony形式で入力

各サンプルはモデル自身のチャットテンプレートで、学習時と同じ完全なHarmony形式に展開しています。 これにより、最終回答だけでなく analysisチャンネル(思考過程) の活性化統計も取得できます。

<|start|>system<|message|>...<|end|>
<|start|>user<|message|>...<|end|>
<|start|>assistant<|channel|>analysis<|message|>{chosen_analysis}<|end|>
<|start|>assistant<|channel|>final<|message|>{chosen_final}<|return|>

なお、マルチターン会話では過去ターンの思考過程は落としています(推論時のチャットテンプレートの 挙動に合わせるため)。対象ターンのCoTは常に保持しています。

サンプルの内訳

活性化統計が入力空間の一部に偏らないよう、3軸で層化して抽出しました。

方針 結果
推論の深さ reasoning_low / medium / high を均等に 各33.3%
言語 かな・漢字の文字比率でサンプル毎に判定し 日本語:英語 = 6:4 ja 60.0% / en 40.0%
データソース 14サブセット全てを各バケット内でラウンドロビン 全14種、各5.2〜10.5%

14サブセットの内訳は daring_anteater / flan / jaster_v1.4.1 / llmjp_extraction_wiki_ja_v0.1 / llmjp_extraction_wiki_ja_v0.2 / llmjp_magpie_sft_v1.0 / logical_math_coding_wizard8x22b / multiturn_calm3 / nemotron_post_v2_stem / nemotron_post_v3_chat / nemotron_post_v3_if / nemotron_post_v3_math / random_to_fixed_multiturn_calm3 / synthetic_jp_en_coding です。 コードブロックを含むサンプルは全体の38.0%でした。

系列長の上限は3,072トークン、合計 5,622,914トークン を使用しています。

量子化の実行

33Bをbf16で読むと62GiB必要で32GBのGPUには載らないため、pipeline="sequential" を使い、 重みをCPU RAMに置いてデコーダ層を1層ずつGPUへ載せながら量子化しています。

from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import QuantizationModifier

recipe = QuantizationModifier(targets="Linear", scheme="NVFP4", ignore=["lm_head"])

oneshot(
    model=model,                  # CPU上にbf16でロード
    dataset=ds,
    recipe=recipe,
    pipeline="sequential",
    sequential_prefetch=True,
    max_seq_length=3072,
    num_calibration_samples=2046,
)

実行時間はRTX 5090で約68秒/サブグラフ x 65サブグラフ = 約73分でした(VRAM使用量は約4GB)。

ライセンス

Apache License 2.0(元モデルおよびcookbookに従います)。

謝辞

Downloads last month
89
Safetensors
Model size
20B params
Tensor type
BF16
·
U8
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Holy-fox/llm-jp-4-33b-thinking-nvfp4

Quantized
(6)
this model