前回まで、ローカルLLMの速度をハードウェアごとに測ってきました。その過程でずっと引っかかっていたことがあります。「速い」と言うとき、いったい何が速いのか、です。

AIの速さの話には、tok/s・TTFT・スループットと、いろいろな単位が出てきます。カタログには「20 tok/s」と書いてある。でも実際に使うと、その通りに感じられないことがある。

そこで今回は「指示してから出力が終わるまでの時間」を分解して、どの数字を見ればいいのかを実測で整理してみました。結論から言うと、同じMac・同じモデルから「速度」が3つ出てきます😳

🧠 この記事でわかること
推論の速度は1つの数字では見られない——2つに分けて、最後に足す
・★入力8千トークンでは、最初の1文字まで39秒かかった
・★待ち時間の割合は7.9%〜95.4%まで入れ替わる。用途で見る数字が逆になる
・★同時に使う人が増えると、1人あたりは7分の1に落ちるのに全体は増える
測り方の罠と、自分で測るための最小手順

「20 tok/s」と書いてあっても

スポンサーリンク

先に、この記事でいちばん言いたいことを出してしまいます。

同じMac・同じモデルなのに「速度」が3つ出た 何を測るかで9.2倍変わる(Apple M4 / Qwen2.5-Coder 7B Q4_K_M) エンジンの申告(生成だけ) 20.85 tok/s 体感(待ち時間込み・1人で使う) 15.51 tok/s 体感(8人が同時に使う) 2.27 tok/s 「このモデルは20 tok/s」と書いてあっても、その通りに感じられるとは限らない ※入力512・出力128トークンでの実測。同時8本は1人が待った時間から逆算

どれも嘘ではありません。測っている場所が違うだけです。ではどう見ればいいのか——順に分解していきます。

用語集

用語意味
トークンAIが文章を扱うときの最小の単位。単語よりやや細かく、日本語だとだいたい1文字〜2文字で1トークン。速度の単位はすべてこれが基準です
tok/s1秒あたり何トークン処理できるか。ただし「読む側」と「書く側」で別の数字なので、どちらの話かを確認する必要があります
pp(読解)prompt processing。与えた指示を読み込む処理。まとめて計算できるので速く、演算力が主役
tg(生成)token generation。返事を1文字ずつ書き出す処理。1つ作るたびにモデル全体を読み直すので、メモリ帯域が主役
TTFTTime To First Token。送信してから最初の1文字が出るまでの時間。「待たされ感」はほぼこれで決まります
レイテンシ1回の応答にかかる時間。使う人が体感するのはこちら
スループット全体で何トークン捌けたか。サービスを提供する側が見るのはこちら。★レイテンシとは逆に動くことがあります
ストリーミングできた文字から順に送り返す方式。全部できるまで待たないので、TTFTが短くなります
ウォームアップ本番の計測前に1回捨てで動かすこと。初回は準備の処理が乗って遅いため。★ただし必要かどうかは環境による(後述)

速度は2つに分けて、最後に足す

推論の処理は、性質のまったく違う2つが直列につながっています。

指示から出力までは、2つの区間でできている 送信 1文字目 書き終わり ① 指示を読む(pp) まとめて計算できる/演算力が主役 ② 1文字ずつ書く(tg) 毎回モデル全体を読み直す/メモリ帯域が主役 総時間 ≒ 入力トークン数 ÷ pp + 出力トークン数 ÷ tg ①の長さがそのまま「最初の1文字までの待ち時間(TTFT)」になる ★ボトルネックが違うので、片方を速くしても体感が変わらないことがある 演算力を上げても②は速くならず、メモリを速くしても①はさほど変わらない

この式さえ持っていれば、あとは実測値を当てはめるだけです。たとえば前回の値(入力512・出力128トークン)を入れると、こうなります。

構成①待ち②出力合計
Apple M41.36秒3.31秒4.68秒
RTX 30601.64秒3.72秒5.36秒
Radeon 780M2.19秒7.46秒9.65秒
M4(設定を誤った例)36.94秒50.59秒87.53秒

Qwen3-30B-A3B Q4_K_M(前回の実測値から計算)。最下段は --n-cpu-moe 48 を付けてしまった場合

実測①:入力が長いほど、1文字目が遠い

ここからは今回の実測です。llama.cppのサーバをMacで立て、ストリーミングで投げて「1文字目が届いた瞬間」を実際に計った結果がこちらです。

入力が長くなると、最初の1文字までの待ち時間はどうなるか 横棒=送信してから1文字目が届くまでの実測秒数(Apple M4 / Qwen2.5-Coder 7B Q4_K_M) 入力 128 tok 0.54秒 入力 512 tok 2.16秒 入力 2,048 tok 8.86秒 入力 8,192 tok 39.13秒 ★入力が4倍になると、待ち時間もほぼ4倍。8千トークンでは1文字目まで39秒 ※実測値とllama.cpp自身の申告値の差は0.05秒以内だった

入力が4倍になると、待ち時間もほぼ4倍。素直な比例です。そして8千トークン読ませると、1文字目が出るまで39秒かかりました😳

8千トークンというと、原稿用紙で30枚ほど。「この資料を要約して」と長い文章を貼り付ける使い方をすると、実際にこうなります。

📌 実測値とエンジンの申告値は一致した
llama.cpp自身が報告する「読み込みにかかった時間」と、外から測った実測TTFTの差は0.05秒以内でした。
つまり手元で動かしている限り、エンジンの申告値をそのまま信じて構いません。ズレが出るのはネットワークを挟んだときです(後述)。

実測②:どちらが効くかは、使い方でそのまま入れ替わる

次に、入力の長さと出力の長さを変えて、①と②の比率がどう動くかを測りました。

どちらが支配的かは、使い方でそのまま入れ替わる 青=入力を読む時間(待ち)/緑=出力を書く時間。同じ機械・同じモデルの実測 入力512 / 出力512 待ち8% 入力512 / 出力128 待ち26% 入力512 / 出力32 待ち59% 入力8,192 / 出力512 待ち60% 入力8,192 / 出力128 待ち85% 入力8,192 / 出力32 待ち95% ★上は「短く読ませて長く書かせる」=出力の速さが効く(待ち7.9%) ★下は「長く読ませて短く答えさせる」=読解の速さが効く(待ち95.4%) 同じ数字を追いかけていては、どちらかで必ず外す

ここが今回いちばん面白かったところです。待ち時間の割合が7.9%から95.4%まで動きます。同じ機械、同じモデルなのに、です。

  • 短く読ませて長く書かせる(入力512・出力512)→ 待ちは7.9%。ほぼ全部が「書く時間」なので、tok/s(生成)が効く
  • 長く読ませて短く答えさせる(入力8192・出力32)→ 待ちが95.4%。生成速度をいくら上げても体感はほとんど変わらない

後者のケースで「もっと速いGPUに変えれば速くなるはず」と考えると、たいてい外します。効くのは読み込みのほうだからです。

用途別に、どちらを見るか

ここまでを、実際の使い方に当てはめて整理します。

使い方支配的なのは見るべき数字
チャットで短い受け答え①待ち時間TTFT。出るまでの一瞬がすべて
コード補完①待ち時間TTFT。数百ミリ秒の差が使用感を決める
長い資料を要約させる①待ち時間pp(読解)。入力1万トークンなら①だけで数十秒
長文の生成・翻訳②出力の速さtg(生成 tok/s)
音声で会話する①がすべてTTFT以前の回で最初の音まで13.5秒→2.7秒にできた
大量のデータを一括処理全体の処理量スループット。1件ずつの速さは気にしない

音声対話の回で「間」を詰めたときも、実は②を速くしたわけではありませんでした。①が終わるのを待たずに喋り始めた——つまり見る数字を変えただけです。

目安は「人が読む速さ」を超えているか

では tok/s は、いくつあれば十分なのでしょうか。ひとつの目安が「人が文章を読む速さ」です。

日本語を黙読する速さは毎分400〜600文字あたりと言われます。1トークンを1〜2文字とすると、おおよそ 7〜10 tok/s という換算になります(トークナイザによって前後します)。

📌 この線を超えると「待っている感じ」が消える
書き出す速さが読む速さを上回れば、読み終わる頃には次が出ている状態になります。
以前の回で Radeon 780M の 13.37 tok/s を「Macほどではないが普通に使える」と書いたのは、この線を超えていたからです。逆に前回の 2.53 tok/s は明確に下回るので、はっきり遅く感じます。

実測③:全体と1人あたりは逆に動く

ここまでは1人で使う話でした。複数人が同時に使うと、話が変わります。

同じサーバに同時に投げる本数を1・2・4・8と変えて測りました。

同時に使う人が増えると、全体と1人あたりは逆に動く 緑=1人あたりの生成速度/青=全体の処理量(どちらも tok/s) 0 5 10 15 20 25 同時1本 同時2本 同時4本 同時8本 21.2 16.4 6.7 3.0 15.6 21.3 18.6 18.1 1人あたりの生成速度 全体の処理量 ★1人あたりは7分の1に落ちるのに、全体は1.37倍まで増える しかも全体は同時2本で頭打ち。それ以上は「全体も増えないのに待ち時間だけ伸びる」領域

1人あたりの速度は 21.2 → 3.0 tok/s と7分の1に落ちます。 待ち時間で言えば8.2秒が56.5秒です。ところが全体で捌けた量は 15.6 → 21.3 tok/s と増えています

同じ機械で、同じ瞬間に、「遅くなった」と「速くなった」が同時に成り立っているわけです。どちらを見るかの問題でしかありません。

★提供する側と使う側で、「速い」の意味が違う
サービスを提供する側はスループット(全体で何件捌けるか)を見ます。同時実行数を増やせばこれは上がる。
一方、使う側が感じるのはレイテンシ(自分の返事が返ってくるまで)です。こちらは同時実行数を増やすほど悪化する。
カタログの数字がどちらを指しているのかは、意識して読む必要があります。

もうひとつ、手元のMacで面白かったのが全体の処理量が同時2本で頭打ちになったことです。4本・8本と増やしても18前後で伸びません。全体も増えないのに、1人あたりの待ち時間だけが7倍になる——完全に損な領域です。

クラウドのサービスがGPUを大量に並べているのは、この頭打ちを避けて「全体を伸ばしつつ1人あたりも守る」ためだと考えると、腑に落ちます。

同じ機械から「速度」が3つ出た

冒頭の図に戻ります。ここまでの実測から、同じMac・同じモデルで3つの数字が出ました。

どこを測ったか数字これを見るのは
エンジンの申告(生成だけ)20.85ハードやモデルを比べるとき
待ち時間込み・1人で使う15.51自分の使い勝手を見積もるとき
8人が同時に使う2.27複数人に提供するとき

9.2倍の開きです。ベンチマークの数字が実感と合わないとき、たいていは測っている場所が違うだけだと分かります。

クラウドのAPIだと、さらに2つ乗る

ここまでは手元で動かす話でした。クラウドのAPIを使う場合、①の待ち時間に2つの要素が追加されます。

追加される要素中身
ネットワーク往復指示を送って結果が返るまでの通信時間。データセンターが遠いほど長い。ほぼ固定で乗る
順番待ち他の利用者と同じGPUを分け合うための待ち。混雑具合で大きく変わる

同じモデル・同じ指示なのに時間帯で速さが違うのは、たいていこの2つ目です。前の章で見たとおり、同時に使う人が増えると1人あたりは落ちる——それがそのまま起きています。

手元で動かす場合、この2つはゼロです。ローカルLLMが「遅いはずなのに使い心地は悪くない」ことがあるのは、待ち行列に並ばなくていいぶんを取り返しているからだと考えられます。

測り方の罠(自分で踏んだもの)

ここまでの数字を出すまでに、いくつか踏んできました。同じところで転ばないように書いておきます。

罠①:自前のストップウォッチは壊れることがある

M4と780Mを比べた回で、自分でコードの前後に時間計測を挟んだところ、pp が 61,000 tok/s という有り得ない値が出ました。

原因はMetalが非同期だったことです。GPUに処理を投げた瞬間に呼び出しが戻ってくるので、実際の計算が終わる前に「終わった」と記録していたわけです。

対処は単純で、推論エンジン自身が出す時間を使うこと。llama.cppはログにこう出してくれます。

prompt eval time =  2121.25 ms /  481 tokens (  4.41 ms per token,  226.75 tokens per second)
       eval time =  6113.34 ms /  128 tokens ( 47.76 ms per token,   20.94 tokens per second)
      total time =  8234.59 ms /  609 tokens

1行目が①、2行目が②です。この2つを足したものが3行目。この3行を読むだけで、この記事の分解はできてしまいます

罠②:「初回は捨てる」が必要かどうかは環境による

Windowsで測った回では、初回の pp が 217 tok/s、2回目以降が 1,218 tok/s と5.6倍も違いました。Vulkanが初回にシェーダを組み立てるためです。だから1回捨てるのが必須でした。

今回、同じことがMacでも起きるだろうと思って、ウォームアップを無効にして起動直後から10回続けて測ってみました

1回目2回目以降の平均
pp(読解)226.75213.68差なし
tg(生成)20.9420.73差なし

予想が外れました。Macでは初回から安定しています。

理由は起動ログにありました。

ggml_metal_library_init: using embedded metal library
ggml_metal_library_init: loaded in 0.008 sec

Metal版はGPU用のプログラムがあらかじめコンパイルされて埋め込まれているので、読み込みは0.008秒。実行時に組み立てるVulkanとは事情が違ったわけです。

📌 つまり「初回は捨てる」は universal なルールではない
Windows(Vulkan)では必須、Mac(Metal)では影響なし
とはいえ捨てて損はないので、環境が変わる可能性があるなら入れておくのが安全です。

罠③:ロード時間は別枠で見る

前回、モデルの読み込み時間が設定ひとつで78.8秒→6.4秒と12倍変わりました。大きな差に見えますが、これは総時間の式には入りません。起動時に1回だけの費用だからです。

常駐させて使うなら体感に無関係ですし、逆に毎回起動し直す使い方なら真っ先に効きます。「自分がどう使うか」で扱いが変わる数字です。

自分で測るための最小手順

手元で確かめるなら、これだけで足ります。

# ① サーバを立てる(llama.cpp は Homebrew で入る)
llama-server -m モデル.gguf --host 127.0.0.1 --port 18099 -c 8192 -np 1

# ② 投げる。返ってくる timings に①と②が入っている
curl -s http://127.0.0.1:18099/completion \
  -H "Content-Type: application/json" \
  -d '{"prompt":"...","n_predict":128,"temperature":0}' | jq .timings

返ってくる timings のうち、見るのはこの4つです。

項目意味
prompt_ms①の時間。そのままTTFTの目安になる
prompt_per_secondpp(読解)の速度
predicted_ms②の時間
predicted_per_secondtg(生成)の速度

ハードやモデルの比較だけなら llama-bench のほうが簡単です。ウォームアップと複数回平均を内蔵しているので、条件を揃える手間が要りません。

llama-bench -m モデル.gguf -p 512 -n 128 -r 3

これまでの実測を1枚に

この1か月ほど測ってきた数字を並べておきます。自分の環境の数字と突き合わせる材料にしてください。

構成・条件pp(読解)tg(生成)出典
Apple M4 / 7B Q4(Metal)23619.55〜20.85p951p962
Apple M4 / 7B(MLX)22.60p962
RTX 3060 / 7B Q4(CUDA)1,21555.94p960
Radeon 780M / 7B Q4(Vulkan)11313.37p960
Apple M4 / 14B Q411010.65p962・今回
Apple M4 / 30B MoE Q437538.62p963
RTX 3060 / 30B MoE Q431234.43p963
Radeon 780M / 30B MoE Q423417.16p963

単位はいずれも tok/s。エンジン・OS・llama.cppの版が揃っていない行があるため、桁感をつかむ目安として見てください

この表を眺めると、ppとtgでは桁がまるで違うのが分かります。RTX 3060は読解で1,215、生成で55.94。20倍以上の開きです。「まとめて計算できるか、1つずつしか進めないか」の差が、そのまま出ています。

まとめ

  • 推論の速度は1つの数字では見られません。 ①指示を読む時間と②1文字ずつ書く時間に分けて、最後に足す。総時間 ≒ 入力÷pp + 出力÷tg
  • 入力が長いほど①が支配的になります。 8千トークンでは1文字目まで39秒。入力が4倍なら待ちもほぼ4倍
  • 待ち時間の割合は7.9%〜95.4%まで入れ替わります。 同じ機械でも、使い方次第で見るべき数字が逆になる
  • 同時に使う人が増えると、1人あたりと全体は逆に動きます。 1人あたりは7分の1に落ちるのに全体は1.37倍。提供側と利用者で「速い」の意味が違う
  • 同じMac・同じモデルから3つの速度が出ました。 20.85 / 15.51 / 2.27——9.2倍の開き。数字が実感と合わないときは、測っている場所を疑うのが早い
  • 測るときはエンジン自身の時間を使う。 自前のストップウォッチはGPUの非同期で壊れます。ただし「初回を捨てる」が要るかは環境次第で、MacのMetalでは影響がありませんでした

tok/s という単位はひとつしかないのに、それが指すものが場面ごとに違う——というのが、今回いちばん腑に落ちた点でした。カタログの数字を見るときは「これはどこを測った数字だろう」と一度考える癖をつけると、見積もりが外れにくくなりそうです😊

それでは、今回はここまで。最後までありがとうございました😊

【実測環境】Apple M4 / macOS 26.5.2 / RAM 32GB(LPDDR5X-7500)/llama.cpp b10090(7347430f4・Metal)。モデルは Qwen2.5-Coder-7B-Instruct Q4_K_M(4.36 GiB)。TTFT・内訳・同時実行はいずれも llama-server にHTTPで投げ、ストリーミングの到着時刻とレスポンスの timings を併記。プロンプトは英文を並べて所定のトークン数に近づけたもので、「入力512」は実測481トークンです。TTFTは3回平均、内訳・同時実行は各1回。同時実行は -np 8 -cb(連続バッチ有効)で計測し、全体の処理量は投げてから全部終わるまでの実時間で割っているため待ち時間を含みます。比較表に載せた他構成の数値は各記事の実測値で、エンジン・OS・llama.cppの版が揃っていない行があります。数値は2026年7月時点のもので、版により変わる可能性があります。