AI公開 データ取得日

スマホのLinux(Termux/PRoot)でllama.cppの生成速度を測った(Qwen2.5 0.5B・1.5B、Q4/Q8)

スマホで小さな言語モデルを動かして、実用になる速さは出るのか。先に答えを書く。

Qwen2.5 の 0.5B は、この記事で置いた目安に届いた。1.5B の Q4_K_M は、生成だけなら届いたが、質問の読み込みまで含めると届かなかった。1.5B の Q8_0 は、速さを測る前に、メモリの条件で止まった。

ただし、この答えには但し書きが多い。測ったのは 2026-09-26 の、1台の端末だけだ。しかも、18条件のうち10条件は、測定中にスワップ(メモリの退避先)に出て無効にした。ここに並べる数字は「この端末・このときのメモリの状態で測った値」で、モデルや量子化どうしの一般的な速さの差を示すものではない。

測り方はこうだ。Android の Termux に PRoot で Ubuntu を入れた環境で、llama.cpp(言語モデルを CPU で動かすソフト)の測定用コマンド llama-bench を使い、Qwen2.5 の 0.5B と 1.5B を、それぞれ Q4_K_M と Q8_0 の2種類の量子化(重みを小さくする方式)で測った。スレッド数は 1・2・4、各条件5回だ。環境と測定の細かい手順は、記事の終わりの付録にまとめてある。

まず、「実用になる速さ」の線を引く

「実用になる」の線は、人や使い方で変わる。そこで、この記事では次の目安を自分で決めた。一般的な基準ではない。

  • 日本語で300字くらいの返答を、30秒以内に書き終えること(1秒に10字)。

チャットのように質問して返事を待つ使い方で、待てる長さとして自分で置いた線だ。判定は返答の生成だけで行い、質問を読み込む時間は含めない(含めた場合の時間は結果の節に書く)。後で出す日本語のトークン数の測定(1トークンあたり 1.51字)で換算すると、300字は約198トークンで、1秒に 6.6 トークン以上の生成が必要になる。

結果: 0.5B は目安に届き、1.5B の Q4_K_M は質問の読み込みを含めると届かなかった

では、実際の値を見てみよう。有効で、5回のばらつきも小さかった条件だけを並べる。どの条件を除いたかは、付録Aに表にした。

モデル スレッド 回 生成(tokens/s、中央値) 5回の幅 最大メモリ(VmHWM)
0.5B-Q4_K_M 4 2回目 19.4 19.2〜19.8 約657MB
0.5B-Q8_0 1 2回目 26.8 25.3〜31.2 約833MB
0.5B-Q8_0 2 1回目 26.1 24.6〜33.6 約833MB
0.5B-Q8_0 2 2回目 28.6 27.6〜28.9 約833MB
0.5B-Q8_0 4 1回目 26.0 24.2〜26.8 約833MB
0.5B-Q8_0 4 2回目 29.0 28.2〜30.5 約833MB
1.5B-Q4_K_M 2 2回目 8.6 8.5〜9.2 約1,299MB
Qwen2.5 の 0.5B と 1.5B を llama.cpp で動かしたときの生成の速さ(tokens/s)を、条件ごとに5回の最小〜最大と中央値で並べた図。0.5B は Q4_K_M の4スレッドが19.4、Q8_0 が1回目26.0〜26.1、2回目26.8〜29.0。1.5B Q4_K_M の2スレッドは8.6で、0.5B のどの条件とも幅が重ならない。0.5B Q8_0 の4スレッドは、1回目と2回目で幅が重ならない。
単位: tokens/s(1秒に生成したトークン数、生成64トークン)。線は5回の最小〜最大、点は中央値。1回目・2回目は同じ日(2026-09-26)に同じ条件で2回回したセッション。測定中に llama-bench のプロセスがスワップに出た条件と、5回の変動係数が15%を超えた条件は載せていない。1.5B Q8_0 は空きメモリの条件を満たさず、測っていない。出典: 筆者が Android スマホの Termux/PRoot(Ubuntu 26.04)上の llama.cpp 8681 で測定

生成の速さを、条件ごとに5回の最小〜最大と中央値で並べたのが上の図だ。0.5B は Q4_K_M・Q8_0 とも中央値が 19.4〜29.0(tokens/s)の範囲に入り、1.5B の Q4_K_M は 8.6 だった。1.5B の幅は、0.5B のどの条件の幅とも重なっていない。

目安と比べるには、tokens/s より日本語の字数と秒のほうが分かりやすい。そこで、日本語の字数に直してみる。

モデル スレッド 回 日本語の字数/秒 300字にかかる秒 質問(256トークン)の読み込み
0.5B-Q4_K_M 4 2回目 29.4 10.2秒 —(比べられない)
0.5B-Q8_0 1 2回目 40.5 7.4秒 3.9秒
0.5B-Q8_0 2 1回目 39.5 7.6秒 1.2秒
0.5B-Q8_0 2 2回目 43.3 6.9秒 1.4秒
0.5B-Q8_0 4 1回目 39.4 7.6秒 —(比べられない)
0.5B-Q8_0 4 2回目 43.9 6.8秒 —(比べられない)
1.5B-Q4_K_M 2 2回目 13.0 23.0秒 15.3秒
  • 0.5B(Q4_K_M・Q8_0): 300字を 6.8〜10.2秒で書き終える計算で、目安に入った。
  • 1.5B の Q4_K_M: 有効だったのは2回目の2スレッドの1条件だけで、300字に 23.0秒。生成だけなら目安に入ったが、256トークン(日本語で約388字)の質問を読み込むのに 15.3秒かかり、合わせると 38.3秒で、30秒の目安には届かなかった。
  • 1.5B の Q8_0: 2回とも、測る直前の空きメモリの条件(付録C)を満たさず、測っていない。

問いへの答えを、もう一度まとめる。0.5B は目安に届いた。1.5B の Q4_K_M は、スワップに出なかった1条件(2回目・2スレッド)で、生成だけなら目安に届いたが、質問の読み込みを含めると38.3秒で目安には届かなかった。1.5B の Q8_0 は速さを測る前にメモリの条件で止まった、という結果だ。

とはいえ、この結果をそのまま一般化はできない。1.5B の Q4_K_M は6条件(2回 × スレッド 1・2・4)のうち5条件が測定中にスワップに出て無効になり、目安に届いたのは2回目・2スレッドの1条件だけだ。1回目の同じ条件は無効だった。0.5B の Q4_K_M も、有効だったのは6条件のうち1条件にとどまる。無効にした条件の一覧と理由は付録Aにある。

もう一つ、同じ条件でも回によって速さが変わった。0.5B Q8_0 の4スレッドでは、1回目(中央値 26.0)と2回目(29.0)で5回の幅が重ならなかった。だから、1.5B Q4_K_M の 8.6 も1回だけの値として見る必要がある。この端末でいつも 1.5B の Q4_K_M が目安に届くとまでは言えない。

スレッド数を変えると、何が変わるか

次に、スレッド数の影響を見てみよう。いちばん有効な条件が多かった 0.5B の Q8_0 で比べる。

  • 生成は、スレッド数を増やしてもほとんど変わらなかった。1スレッド 26.8、2スレッド 26.1・28.6、4スレッド 26.0・29.0(tokens/s、中央値。2つある所は1回目・2回目)。
  • prompt 処理(質問を読み込む速さ)は、1スレッドの 63.6・65.6 から、2スレッドで 208.5・189.0 に上がった。4スレッドは2回とも変動係数が15%を超えたので、比べていない。

この端末で、このプロセスが使える CPU は4コア(0・1・4・5番)だった。2スレッドで prompt 処理が 3.28倍・2.88倍(1回目・2回目)になった理由は確かめていない。

量子化を変えると: 0.5B では Q8_0 の方が速かった(4スレッドの生成だけ)

量子化の違いも見ておく。ファイルは Q4_K_M(0.49GB)の方が Q8_0(0.68GB)より小さい。ところが、比べられた4スレッドの生成では、Q8_0 の方が速い結果だった。2回目どうしで Q4_K_M 19.4、Q8_0 29.0、1回目の Q8_0 は 26.0 だ(tokens/s、中央値)。

0.5B の Q4_K_M で有効だったのはこの1条件だけで、ほかのスレッド数では比べられていない。理由も確かめていない。そのため、「この端末の4スレッドの生成では、Q8_0 の方が速かった」ところまでにとどめる。

日本語は英語より、トークン数がどれだけ多いか

最後に、日本語と英語の違いを見る。言語モデルは文をトークンという単位に分けて扱うので、同じ意味でもトークンが多い言語は、同じ tokens/s でも書き終えるのが遅くなる。そこで、同じ意味の日本語と英語の短い文を6組、自分で作り、llama.cpp の llama-tokenize で数えた(0.5B と 1.5B は同じトークン ID になった)。

  1. 明日の朝は雨が降るので、傘を持って出かけます。 / It will rain tomorrow morning, so I will take an umbrella when I go out.
  2. このスマホでは、小さな言語モデルが手元で動きました。 / A small language model ran locally on this smartphone.
  3. 駅前の本屋が閉店したので、新しい漫画は通販で買っています。 / The bookstore in front of the station has closed, so I now buy new manga online.
  4. ファイルを保存する前に、名前と日付を確認してください。 / Before saving the file, please check the name and the date.
  5. 週末は友だちと三人で、新しく出たゲームを遊びました。 / On the weekend, I played a newly released game with two friends.
  6. 電池の残りが少ないときは、画面の明るさを下げると長持ちします。 / When the battery is low, lowering the screen brightness makes it last longer.
# 日本語の字数 日本語のトークン 英語の単語数 英語のトークン 日÷英
1 23 16 15 17 0.94
2 26 15 9 10 1.50
3 29 21 16 18 1.17
4 27 15 11 13 1.15
5 26 19 12 14 1.36
6 31 21 13 15 1.40
計 162 107 76 87 1.23

6組の合計では、日本語は英語の 1.23倍のトークンになった。組ごとには 0.94〜1.50倍と幅があり、1組目は日本語の方が少ない。日本語は1トークンあたり 1.51字だった。前に出した表の「日本語の字数/秒」と「300字にかかる秒」は、この 1.51字で換算している。6組だけの結果なので、文の内容が変われば比は変わる。

数字を見るときの注意

  • 1台の端末・1日の測定だ。 同じ条件でも、0.5B Q8_0 の4スレッドの生成は、1回目が 24.2〜26.8、2回目が 28.2〜30.5 で、5回の幅が重ならなかった。同じ日の中でも、回によってこの程度の差が出ている。
  • 温度と充電の状態は確かめられていない。 スマホは熱で速さが変わることがある。
  • メモリの状態に左右された。 各条件・各モデルの判定の直前に記録した空きメモリは 2.22〜3.79GB の間で動き、無効の条件が多かったことにも影響している可能性がある(ほかのプロセスの動きは記録していないので、確かめていない)。
  • VmSwap は目安の値だ。 Linux の proc のマニュアルには、この値は不正確なことがあると書かれている。また 0.5秒ごとに読んだので、それより短い間の退避は記録から漏れている可能性がある。
  • 速さだけを測った。 返答の中身の良し悪しは比べていない。0.5B と 1.5B では、返せる内容の質も違うと考えられる(今回は確かめていない)。
  • 短い入力・短い出力だ。 prompt 256トークン・生成64トークンで、長い会話の後半のように読み込む量が増えると、速さは変わる。

まとめ

スマホ(Termux/PRoot)の1台で測った範囲では、Qwen2.5 の 0.5B は「日本語300字を30秒以内」の目安に届いた。1.5B の Q4_K_M は、有効だった1条件で生成だけなら届いたが、質問の読み込みを含めると38.3秒で届かなかった。1.5B の Q8_0 は、メモリの条件を満たさず測っていない。ただし、条件の多くが測定中のスワップで無効になり、同じ条件でも回によって速さが変わったので、この結果は「この端末・このときの状態の値」として読んでほしい。

付録A 無効にした条件: 18条件のうち10条件

Linux では、メモリが足りなくなると、プロセスのメモリの中身の一部をスワップ(退避先の領域)に移すことがある。測定中にモデルの重みが退避されると、その分だけ速さが落ちるおそれがある。そこで、測っている llama-bench のプロセスがスワップに出た条件は、値を使わずに無効にした。さらに、同じ条件の5回の変動係数(標準偏差÷平均)が15%を超えたものは「比べられない」とした。

1回目で有効な条件が少なかったため、同じ条件でもう1回だけ全体を回すことと、両方の回の扱い(両方有効なら両方載せる。無効の値は比べるのに使わない)を、2回目の前に決めて記録した。やり直しはこの1回だけだ。

回 モデル スレッド 測定中のスワップ(VmSwap の最大) prompt 処理 生成
1回目 0.5B-Q4_K_M 1 280MB → 無効 無効 無効
1回目 0.5B-Q4_K_M 2 123MB → 無効 無効 無効
1回目 0.5B-Q4_K_M 4 67MB → 無効 無効 無効
1回目 0.5B-Q8_0 1 0 kB 63.6(変動係数 3.6%) 比べられない(変動係数 24.8%)
1回目 0.5B-Q8_0 2 0 kB 208.5(変動係数 7.4%) 26.1(変動係数 13.6%)
1回目 0.5B-Q8_0 4 0 kB 比べられない(変動係数 16.9%) 26.0(変動係数 4.0%)
1回目 1.5B-Q4_K_M 1 762MB → 無効 無効 無効
1回目 1.5B-Q4_K_M 2 434MB → 無効 無効 無効
1回目 1.5B-Q4_K_M 4 342MB → 無効 無効 無効
1回目 1.5B-Q8_0 — 測っていない(空きメモリ不足: 空き 3.57GB の半分 < ファイル 1.89GB) — —
2回目 0.5B-Q4_K_M 1 98MB → 無効 無効 無効
2回目 0.5B-Q4_K_M 2 97MB → 無効 無効 無効
2回目 0.5B-Q4_K_M 4 0 kB 比べられない(変動係数 47.1%) 19.4(変動係数 1.1%)
2回目 0.5B-Q8_0 1 0 kB 65.6(変動係数 5.5%) 26.8(変動係数 8.3%)
2回目 0.5B-Q8_0 2 0 kB 189.0(変動係数 9.8%) 28.6(変動係数 2.0%)
2回目 0.5B-Q8_0 4 0 kB 比べられない(変動係数 20.7%) 29.0(変動係数 3.4%)
2回目 1.5B-Q4_K_M 1 577MB → 無効 無効 無効
2回目 1.5B-Q4_K_M 2 0 kB 16.8(変動係数 1.1%) 8.6(変動係数 2.9%)
2回目 1.5B-Q4_K_M 4 36MB → 無効 無効 無効
2回目 1.5B-Q8_0 — 測っていない(空きメモリ不足: 空き 2.90GB の半分 < ファイル 1.89GB) — —
  • 「測定中のスワップ」は、llama-bench のプロセスの /proc/<pid>/status にある VmSwap を 0.5秒ごとに読んだ最大値だ(MB は kB を1024で割った値)。
  • 空きメモリ・スワップ・ファイルサイズの GB は10億バイトだ(この記事の GB はすべて同じ)。
  • 0.5B の Q8_0 は、2回とも3条件すべてでスワップに出なかった。0.5B と 1.5B の Q4_K_M は、それぞれ6条件のうち5条件でスワップに出た。スワップに出た時点は、起動から2秒後〜133秒後とまちまちで、なぜ Q4_K_M の方で多かったのかは確かめていない。
  • 測る前から、端末全体ではスワップが使われていた(1回目の開始時点で、約12.9GB のうち約8.8GB)。測定の前の準備の段階でも、llama.cpp の導入とモデルの取得をしていた間(llama.cpp はまだ動かしていない)に、端末全体のスワップの使用量が約0.87GB 増えていた。同じ時間帯に別の作業も動いていたため、どのプロセスによるものかは確かめていない。

付録B 測った環境

項目 内容
端末 Android スマホ(Termux + proot-distro)
Linux Ubuntu 26.04 LTS(aarch64)、カーネルの表示は 6.17.0-PRoot-Distro
CPU lscpu の表示は Qualcomm・8コア・最大 4.74GHz。このプロセスが使えたのは4コア(nproc が 4、使える CPU は 0-1,4-5)
メモリ 約11.6GB(/proc/meminfo の MemTotal)。各条件・各モデルの判定の直前に記録した空き(MemAvailable)は 2.22〜3.79GB
llama.cpp Ubuntu のパッケージ llama.cpp 8681+dfsg-1(llama-bench の表示は version 8681 (Debian))。CPU のバックエンド libggml-cpu-armv9.2_2.so が読み込まれた
モデル Hugging Face の Qwen 公式リポジトリ Qwen/Qwen2.5-0.5B-Instruct-GGUF・Qwen/Qwen2.5-1.5B-Instruct-GGUF の q4_k_m・q8_0。ライセンスは両方とも apache-2.0

同じ端末で静的サイトジェネレーターのビルド時間を測った記事もある(スマホのLinux(Termux/PRoot)でHugo・Eleventy・Astroのビルド時間を測った)。

モデルのファイルサイズは 0.5B が 0.49GB(Q4_K_M)・0.68GB(Q8_0)、1.5B が 1.12GB・1.89GB だ。取得したファイルの sha256 は、Hugging Face の API が示す値と一致した。3B は、同じ Qwen の GGUF リポジトリで表示されるライセンスが other(qwen-research)で apache-2.0 ではないため、対象にしていない。

付録C 測り方

  • コマンド: llama-bench -m <モデル> -t <1|2|4> -r 5 -p 256 -n 64 -mmp 0 -o json。prompt 処理は256トークン、生成は64トークンだ。llama-bench は各テストを -r の回数だけ繰り返し、JSON では1回ごとの値も出す。測る前に慣らしの実行が自動で入る(llama-bench --help の --no-warmup の説明)。
  • -mmp 0: モデルをファイルの割り当て(mmap)ではなく、プロセスのメモリに読み込む設定だ。こうすると重みが退避されたときに VmSwap に現れるので、スワップの判定に使える。
  • 空きメモリの条件: 各モデルは、測る直前の MemAvailable の半分以下のファイルサイズのときだけ測った(測定の前に決めた条件)。
  • 順番: 1回のセッションで 0.5B Q4_K_M → 0.5B Q8_0 → 1.5B Q4_K_M → 1.5B Q8_0、各モデルの中はスレッド 1 → 2 → 4 の順だ。1回目は 12:01〜12:28、2回目は 12:36〜12:49(日本時間)だった。
  • 最大メモリ: VmHWM(プロセスが使った実メモリの最大値)を 0.5秒ごとに読んだ最大だ。
  • 測定中は、測定を動かしているプログラム(AI エージェントの実行環境)が常駐していた。それ以外の作業はしていない。

出典