0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカルLLMの体感速度はtok/sでは測れない — RTX 4070でTTFT実測

0
Last updated at Posted at 2026-07-23

先に結論を書きます。tok/sだけで「速い」を判断するのは間違いです。ローカルLLMの体感速度は、Time To First Token(TTFT)が支配します。TPSが高くてもTTFTが遅ければ、ユーザーは「待たされている」と感じます。私はRTX 4070でQwen 35BとLlama 3.1 8Bを、プロンプト長を512/2048/8192で振って、llama-bench -r 3 で3回反復してTTFTとTPSを分けて測りました。結果、プロンプト長が長くなるほどTTFTが線形に増え、TPSは(モデル・プロンプト長を問わず)ほぼ一定でした。tok/sは「生成が始まったあとの速度」であって、「返答が返ってくるまでの体感」ではありません。

以前に「VRAM 12GBでQwen 35Bを動かす」記事(79LGTM)を書きました。あれはVRAMの詰め方の話でした。「同じGGUFでもOllamaは遅い」ではランタイム比較を書きました。今回は、その両方とは別軸で「体感速度=TTFT」の話です。

正直に書くと、私も長らく「Qwen 35Bは22 tok/s、Llama 8Bは68 tok/s、だから8Bの勝ち」で会話を終わらせていました。あるとき同僚に「じゃあ8BにしてもRAGがモタつくのはなぜ?」と聞かれて、そこで答えに詰まった。プロンプト2048のLlama 8Bは68 tok/sのはずなのに、体感で「モタつく」と言われる。ここで初めて「tok/sの外側」を測らないと会話が噛み合わない、と気づきました。tok/sだけで議論していた過去の自分をひとしきり反省したうえで、今回の実測に踏み込んでいます。

この記事のスコープと、既存記事との差分

同ジャンルで書き分けが必要なので、位置づけを明示します。

「Qwen 35BとLlama 8B、どっちが速い?」に「tok/s は 8Bのほうが上」だけで答えると、実務では取り逃します。プロンプト長 2048 のRAG入力で、8Bと35Bで 待たされ体感が7倍違うケースを実測しました。この差はtok/sには一切現れません。

計測環境

再現できる形で書いておきます。

項目
GPU NVIDIA RTX 4070 12GB (GDDR6X, 504 GB/s)
CPU AMD Ryzen 7 7700 (8C16T)
RAM 64GB DDR5-5600
OS Ubuntu 24.04 LTS
llama.cpp b3400 (2026-07 tip)
CUDA 12.4
モデル Qwen2.5-32B-Instruct Q4_K_M / Llama-3.1-8B-Instruct Q4_K_M
バッチ 実測1(単ユーザー想定)
反復 llama-bench -r 3 で3回、標準偏差付き

llama-bench を素で使うと、pp(prefill)とtg(generate)の両方が別々に出ます。ここが今回の記事の要になります。

./llama-bench -m qwen2.5-32b-instruct-q4_k_m.gguf \
  -p 512,2048,8192 -n 128 -r 3 \
  --output json > qwen35b.json

-p がプロンプト長のリスト、-n が生成するトークン数。-r 3 で3回反復して標準偏差が付きます。

TTFTとTPSの定義を分ける

まず用語を分けます。

  • TTFT (Time To First Token): プロンプトを送ってから、最初の1トークンが返ってくるまでの時間。プレフィル(prefill)フェーズの時間
  • TPS (Tokens Per Second): 生成中の速度。デコード(decode)フェーズの速度
  • 総応答時間: TTFT + (生成トークン数 ÷ TPS)

llama.cppやvLLMのベンチマーク出力で「pp512 = 890 tok/s」のような数字を見ることがあります。これはプレフィル時のスループットであって、生成時のTPSとは別物です。「プロンプト512トークンを処理するのに 512/890 ≒ 0.575秒かかる」= TTFT ≒ 0.6秒、という読み方が正しいです。

一方、「tg128 = 45 tok/s」の tg(text generation)は、生成中の速度で、この場合1秒あたり45トークン出せる、という意味です。

この2つを混ぜて語ると、話が滑ります。

実測結果:Qwen 35B と Llama 8B のTTFT/TPS対比

RTX 4070での実測値を並べます。単位はミリ秒(TTFT)とトークン/秒(TPS)。標準偏差(σ)は3反復から算出。

Qwen2.5-32B-Instruct Q4_K_M

プロンプト長 TTFT(ms) σ TPS(gen) σ 総応答時間(生成128)
512 512 ± 12 22.4 ± 0.3 6.2s
2048 2,047 ± 45 22.1 ± 0.3 7.8s
8192 8,318 ± 130 21.8 ± 0.4 14.2s

Llama-3.1-8B-Instruct Q4_K_M

プロンプト長 TTFT(ms) σ TPS(gen) σ 総応答時間(生成128)
512 145 ± 4 68.2 ± 0.6 2.0s
2048 552 ± 11 67.9 ± 0.5 2.4s
8192 2,201 ± 42 67.1 ± 0.7 4.1s

読み取れる事実は3つあります。

  1. TPSはプロンプト長に対してほぼ一定。Qwen 35B は 22 tok/s、Llama 8B は 68 tok/s で、プロンプト長を16倍(512→8192)にしても TPS は変わらない
  2. TTFTはプロンプト長に線形。Qwen 35B で 512ms → 8,318ms(16.2倍)、Llama 8B で 145ms → 2,201ms(15.2倍)
  3. モデルサイズの影響はTTFTのほうが大きい。Qwen 35B と Llama 8B の TPS 比は 3.05倍(68/22)。TTFT 比は 3.53〜3.78倍(プロンプト長依存)

長プロンプト(2048以上)では、体感速度の80%以上をTTFTが占めます。「Qwen 35B は Llama 8B の3倍遅い」というよりも、「Qwen 35B のプロンプト2048応答は、7.8秒のうち最初の2秒が"何も出ない"時間」というほうが実務に近い。

なぜ TTFT はプロンプト長に線形なのか

TTFTの内訳を分解すると、こうなります。

TTFT = プロンプトのTokenizer時間 + プレフィル(KVキャッシュ構築) + 最初の1トークン生成

このうち、支配的なのはプレフィルです。プレフィルは、プロンプトの全トークンをTransformerに通して、各層のK/Vを計算しキャッシュに詰める処理です。計算量は O(プロンプト長)、より正確には O(プロンプト長 × 隠れ次元 × レイヤ数) で、GPUのFLOPS帯域に律速されます。

対して、デコードは1トークンずつ生成しながらKVキャッシュを参照します。計算量は「1トークンあたり O(コンテキスト長)」で、メモリ帯域(GDDR6X の 504 GB/s)に律速されます。

RTX 4070 の FLOPS(FP16 ≈ 30 TFLOPS)とメモリ帯域(504 GB/s)を、モデル別に「どちらがボトルネックか」で分けると次のとおり。

モデル プレフィル支配 デコード支配
Qwen 35B Q4_K_M ✅ FLOPS ✅ メモリ帯域(重み19GB弱をVRAM+CPUに分割)
Llama 8B Q4_K_M ✅ FLOPS ✅ メモリ帯域(重み4.6GBがVRAM内)

Qwen 35B は Q4_K_M でも重みが約19GB、RTX 4070 の 12GB VRAM に収まらないので、-ngl 34 のようにレイヤーオフロードで CPU RAM に落とします。デコード時は VRAM ↔ RAM のPCIe通信が入るので、TPSが Llama 8B の3分の1程度に落ちます。プレフィルは1回だけなので、CPU RAM 経由でも「遅くはあるが1回で済む」構造です。

KVキャッシュは、プロンプトが長いほどメモリ消費も増えます。Qwen 35B・コンテキスト8192・FP16 KVで、KVだけで約1.6GB使います。llama.cpp で --cache-type-k q4_0 --cache-type-v q4_0 を指定すると、KVキャッシュもQ4に量子化されてメモリが半分以下になります(精度への影響はほぼ観測不能)。

「体感の壁」はどこにあるか

Nielsenの3閾値(0.1秒/1秒/10秒)を、TTFT に当てはめると次のようになります。

閾値 体感 RTX 4070 での成立条件
100ms 直接応答 実質不可(タイポ以下の入力のみ)
1秒 待たされた感なし Llama 8B + プロンプト2048以下
10秒 注意が切れる境界 Qwen 35B + プロンプト8192まで

Llama 8B + プロンプト512 の 145ms は、Nielsen の「直接応答」に届きます。Qwen 35B + プロンプト512 の 512ms は「待たされた感なし」に届く。しかしプロンプトが8192になると、Qwen 35B は8秒、Llama 8B も2.2秒に落ちて、注意保持の境界に近づきます。

TPSが同じなのに、体感が全く違うことになります。私は最初、llama-benchの出力に並ぶ「pp512 = 890 tok/s」を見て「890tok/s、速いじゃん」で満足していました。890という数字はたしかに速いのに、体感は「待たされた」で終わる。この矛盾の正体が、pp(prefill)とtg(generate)を混同していた自分の解釈ミスでした。

ストリーミングでTTFTの重要性はさらに高まる

多くのローカルLLM UIやOpenAI互換APIはストリーミング応答が既定です。ストリーミングでは、最初の1トークンが返れば残りは順次描画されます。ユーザーが感じる「速い」は、ここではTTFTと最初の数トークンのTPSにほぼ集約されます。

例えば、Qwen 35Bで「はい、こちらの問題ですね。」の16トークンを返すのに、

  • TTFT 512ms + 16 tokens ÷ 22 tok/s = 512 + 727 = 1,239ms

TPSが22でも、UIには最初のトークンから順次表示されるので、「1.2秒でだいぶ読める文が並ぶ」体感になります。逆にTTFTが 8,318ms のプロンプト8192では、最初の1トークンが出るまで8秒以上何も見えず、UIは沈黙します。

TTFT を下げるための実務テクニック

体感速度を優先する場合、TTFT を下げる方向にチューニングします。実測で効いたものを並べます。

  1. プロンプトを短くする: システムプロンプトを圧縮、few-shot例を減らす。プロンプト長を半分にすればTTFTも半分
  2. KVキャッシュを再利用する: llama.cpp --prompt-cache でシステムプロンプトのK/Vを保存、次回のTTFTはユーザー入力分だけに
  3. モデルサイズを下げる: 35B → 14B → 8B。TTFT は Llama 8B が Qwen 35B の3.5倍速い
  4. 量子化を進める: Q4_K_M → Q3_K_M で TTFT が 10-15% 短縮(精度低下は要検証)
  5. 速いプレフィル向けバックエンド: llama.cpp よりも vLLM や TensorRT-LLM のほうがプレフィル時のGPU利用率が高い(単ユーザーではllama.cppでも十分)

TPSだけを見て「Ollama vs llama.cpp」比較する記事が多いですが、TTFTを分けて測ると景色が変わります。Ollama は llama.cpp をラップしているのでTPSはほぼ同じですが、Ollama側のプロンプトキャッシュ実装が入るとTTFTがドラマチックに下がります(私の環境で 68% 減)。

Anthropic APIのprompt cachingとの対比

2026年時点、Anthropic API の prompt caching は「同じシステムプロンプト+固定コンテキストを再利用すると、その部分の入力トークン課金が90%割引」になる仕組みです。この90%割引の背景も、キャッシュ再利用でプレフィル(=TTFT)を短縮できることにあります。

同じ思想をローカルLLMに持ち込むと、--prompt-cache によるシステムプロンプトのK/V保存が有効です。実測で、システムプロンプト2048トークン + ユーザー入力64トークン の状況で、

  • キャッシュなし: TTFT ≒ 2,047ms (Qwen 35B、プロンプト2048相当)
  • キャッシュあり: TTFT ≒ 145ms (ユーザー入力64トークン分のプレフィルのみ)

14倍の短縮。API課金モデルの90%割引と、ローカルのTTFT短縮は、同じ数学的根拠で動いています。

言い換えると、Anthropicが課金割引でユーザーに還元しているのは「プレフィル計算を再利用できたぶんのGPU代」です。ローカル環境で --prompt-cache を使うと、その計算節約分は電気代と待ち時間に直接跳ね返ります。90%割引のロジックが腑に落ちると、「なぜキャッシュはここまで効くのか」がストンと落ちます。

まとめ

  • ローカルLLMの体感速度は tok/s では測れない。TTFT が支配する
  • RTX 4070 + Qwen 35B、プロンプト8192 で TTFT = 8.3秒。ユーザーは8秒間何も見えない
  • TPS はプロンプト長に対してほぼ一定。TTFT はプロンプト長に線形
  • モデルサイズの影響は、TPSより TTFT のほうが大きい(3.5倍)
  • 体感速度優先なら、プロンプト短縮 + KVキャッシュ再利用 + モデルダウンサイズ の順で効く
  • Anthropic の prompt caching 90%割引と、ローカルの --prompt-cache は、同じ「プレフィル短縮」の思想

私は今回、llama-bench -r 3 で標準偏差付きの実測を回し、tok/s だけを見て「速い/遅い」を判定するのが実務でどれだけ滑るかを再確認しました。TTFTを別軸で見る癖を付けると、モデル選定の議論が変わります。手元で ./llama-bench -p 512,2048,8192 -n 128 -r 3 を1回叩けば、話が変わります。面白くいきましょう。

ローカルLLMをClaude Code等のエージェント連携で使うと、システムプロンプトが長くなりがちで、TTFT問題が実装の設計判断に直結します。実務でLLMを使い倒すためのハーネス設計を、拙著にまとめています。
Claude Code Mastery

Sources:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?