前回まで、ローカル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」と書いてあっても
先に、この記事でいちばん言いたいことを出してしまいます。
どれも嘘ではありません。測っている場所が違うだけです。ではどう見ればいいのか——順に分解していきます。
用語集
| 用語 | 意味 |
|---|---|
| トークン | AIが文章を扱うときの最小の単位。単語よりやや細かく、日本語だとだいたい1文字〜2文字で1トークン。速度の単位はすべてこれが基準です |
| tok/s | 1秒あたり何トークン処理できるか。ただし「読む側」と「書く側」で別の数字なので、どちらの話かを確認する必要があります |
| pp(読解) | prompt processing。与えた指示を読み込む処理。まとめて計算できるので速く、演算力が主役 |
| tg(生成) | token generation。返事を1文字ずつ書き出す処理。1つ作るたびにモデル全体を読み直すので、メモリ帯域が主役 |
| TTFT | Time To First Token。送信してから最初の1文字が出るまでの時間。「待たされ感」はほぼこれで決まります |
| レイテンシ | 1回の応答にかかる時間。使う人が体感するのはこちら |
| スループット | 全体で何トークン捌けたか。サービスを提供する側が見るのはこちら。★レイテンシとは逆に動くことがあります |
| ストリーミング | できた文字から順に送り返す方式。全部できるまで待たないので、TTFTが短くなります |
| ウォームアップ | 本番の計測前に1回捨てで動かすこと。初回は準備の処理が乗って遅いため。★ただし必要かどうかは環境による(後述) |
速度は2つに分けて、最後に足す
推論の処理は、性質のまったく違う2つが直列につながっています。
この式さえ持っていれば、あとは実測値を当てはめるだけです。たとえば前回の値(入力512・出力128トークン)を入れると、こうなります。
| 構成 | ①待ち | ②出力 | 合計 |
|---|---|---|---|
| Apple M4 | 1.36秒 | 3.31秒 | 4.68秒 |
| RTX 3060 | 1.64秒 | 3.72秒 | 5.36秒 |
| Radeon 780M | 2.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文字目が届いた瞬間」を実際に計った結果がこちらです。
入力が4倍になると、待ち時間もほぼ4倍。素直な比例です。そして8千トークン読ませると、1文字目が出るまで39秒かかりました😳
8千トークンというと、原稿用紙で30枚ほど。「この資料を要約して」と長い文章を貼り付ける使い方をすると、実際にこうなります。
llama.cpp自身が報告する「読み込みにかかった時間」と、外から測った実測TTFTの差は0.05秒以内でした。
つまり手元で動かしている限り、エンジンの申告値をそのまま信じて構いません。ズレが出るのはネットワークを挟んだときです(後述)。
実測②:どちらが効くかは、使い方でそのまま入れ替わる
次に、入力の長さと出力の長さを変えて、①と②の比率がどう動くかを測りました。
ここが今回いちばん面白かったところです。待ち時間の割合が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人あたりの速度は 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.75 | 213.68 | 差なし |
| tg(生成) | 20.94 | 20.73 | 差なし |
予想が外れました。Macでは初回から安定しています。
理由は起動ログにありました。
ggml_metal_library_init: using embedded metal library ggml_metal_library_init: loaded in 0.008 sec
Metal版はGPU用のプログラムがあらかじめコンパイルされて埋め込まれているので、読み込みは0.008秒。実行時に組み立てるVulkanとは事情が違ったわけです。
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_second | pp(読解)の速度 |
predicted_ms | ②の時間 |
predicted_per_second | tg(生成)の速度 |
ハードやモデルの比較だけなら llama-bench のほうが簡単です。ウォームアップと複数回平均を内蔵しているので、条件を揃える手間が要りません。
llama-bench -m モデル.gguf -p 512 -n 128 -r 3
これまでの実測を1枚に
この1か月ほど測ってきた数字を並べておきます。自分の環境の数字と突き合わせる材料にしてください。
| 構成・条件 | pp(読解) | tg(生成) | 出典 |
|---|---|---|---|
| Apple M4 / 7B Q4(Metal) | 236 | 19.55〜20.85 | p951・p962 |
| Apple M4 / 7B(MLX) | — | 22.60 | p962 |
| RTX 3060 / 7B Q4(CUDA) | 1,215 | 55.94 | p960 |
| Radeon 780M / 7B Q4(Vulkan) | 113 | 13.37 | p960 |
| Apple M4 / 14B Q4 | 110 | 10.65 | p962・今回 |
| Apple M4 / 30B MoE Q4 | 375 | 38.62 | p963 |
| RTX 3060 / 30B MoE Q4 | 312 | 34.43 | p963 |
| Radeon 780M / 30B MoE Q4 | 234 | 17.16 | p963 |
単位はいずれも 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月時点のもので、版により変わる可能性があります。