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を手元のミニPCで速くした記録

0
Posted at

砂嵐から文章が現れる — 拡散型LLMを手元のミニPCで速くした記録

ChatGPTのようなAIが文章を書くところを見たことがあると思う。左から右へ、一語ずつ吐き出していく。あれは演出ではなく本当にそう動いていて、次の一語が決まるまで、その次の語はまだこの世に存在しない。

DiffusionGemmaというモデルは、そこが違う。

まず256語分の、でたらめな語の並びを用意する。砂嵐のようなものだ。そこからモデルは、自信のある場所だけを少しずつ正しい語に置き換えていく。何度か繰り返すと、砂嵐の中から文章が浮かび上がってくる。写真の現像に近い。

一度に256語をまとめて相手にするのだから、一語ずつ書くより速いに違いない。私はそう思っていた。手元のミニPCで動かしてみたら、毎秒26語だった。

26語という数字の内訳

使ったのはAMDのRyzen AI MAX+ 395というCPUと、そこに内蔵されたRadeon 8060Sというグラフィックス機能である。単体のグラフィックスカードは挿していない。メモリは96GBあって、CPUとGPUがそれを分け合って使う。モデルのファイルは15.6GBあるので、ふつうのゲーム用GPUだと積んでいるメモリに入りきらない。この構成なら丸ごと乗る。

動かすと、1回の「磨き」に435ミリ秒かかっていた。そして磨きは22回繰り返される。256語を作るのに約9.6秒。割り算すると毎秒26語になる。

つまり遅さの正体は、一度に256語を作ることではなく、それを22回やり直すことのほうにあった。速くする道は二つある。1回あたりを短くするか、回数を減らすか。

まず1回の435ミリ秒を疑った。

犯人はCPUに違いない、と思った

拡散型のモデルは、磨きのたびに「各位置でどの語がどれくらい有力か」という点数表を作る。この点数表がとにかく大きい。位置が256個あって、語の候補が262144種類ある。掛けると6700万個の数値になる。

そのあと、位置ごとに「どれくらい迷っているか」を計算する。迷いの度合いが小さい位置から順に確定させていくためだ。この計算のためにプログラムは6700万個の数値を3回なめる。しかも指数関数という重めの計算を、合わせて1億3400万回ほど呼ぶ。

紙の上で見積もった。16スレッドで分担しても100ミリ秒から150ミリ秒はかかるはずだ。435ミリ秒のうち3割がCPUで、GPUは3割ほど手を止めて待っている——そういう絵を私は描いていた。

描いたところで、測っていなかった。

そこで、プログラムに時計を入れた。GPUに計算を投げてから結果が返るまでと、その後CPUが点数表をなめている間と、二つの区間をストップウォッチで挟むだけの、10行に満たない追加である。

diffusion_profile: 22 steps, 435.05 ms/step
  | gpu  373.51 ms (85.9%)
  | host  61.50 ms (14.1%)

CPUは62ミリ秒だった。見積もりの半分以下である。

外れた理由自体は、あとから考えれば当たり前だった。指数関数の計算は今どきのCPUなら何個かまとめて同時に処理できるし、16スレッドが本当に16倍近く効いていた。それでも、2倍以上外していたという事実は残る。この見積もりを信じたまま次の一手を選んでいたら、私は「CPUの計算を減らす」ことに時間を使っていた。実際にはそこには62ミリ秒しか置かれていない。

時計を入れる作業は10分で終わった。

天井の測り方

CPUが14%なら、残る86%、373ミリ秒のGPUが本丸である。ではこれは削れるのか。

削れるかどうかを言うには、比べる相手がいる。「このGPUが256語を一度に処理するとき、どれだけ速くできるのが上限か」を知りたい。

幸い、llama.cppにはllama-benchという測定用のプログラムが付いている。これに同じモデルを渡して、256語をまとめて処理させると毎秒969語と出た。裏返すと、256語で264ミリ秒。これがこのハードの実力である。

実測は373ミリ秒だった。差は約110ミリ秒。

この110ミリ秒が無駄なのかというと、そうではなかった。拡散型のモデルは、ふつうのモデルがやらない計算を2つ余分にやっている。

一つめは、262144種類の語それぞれの点数を出す計算を、1か所ではなく256か所すべてでやること。ふつうのAIは次の1語だけ決めればいいので、この計算は1回で済む。拡散型は全部の位置で同時にやる。

二つめは、前回の磨きで「こう思った」という情報を次の磨きに渡す仕組みがあること。これも大きな掛け算になる。

二つの計算量を数えると、それぞれ3780億回のかけ算とたし算だった。このGPUがこのモデルで実際に出している速度から逆算すると、合わせて90〜105ミリ秒。差の110ミリ秒とほぼ一致する。

つまりGPUの373ミリ秒は、264ミリ秒の「このハードの限界」と、100ミリ秒ほどの「拡散型である以上どうしても必要な計算」で、ほぼ埋まっていた。

念のため確かめたことが二つある。一つは、行列演算を速く回すための専用回路がちゃんと使われているか。起動時のログにmatrix cores: KHR_coopmatと出ていたので、使われていた。もう一つは、注意機構を高速化する「フラッシュ注意」という仕組みが効いているか。こちらはオンで376.08ミリ秒、オフで377.42ミリ秒。差は誤差の範囲で、効いていなかった。ただし注意機構はそもそもボトルネックではないので、直しても得はない。

1回あたりの435ミリ秒は、ほぼ限界だった。

名簿に載っていない

一つだけ、構造的に損をしている場所が見つかった。

さきほどのCPUの62ミリ秒は、本来GPUの中で片付けられる計算である。llama.cppにはそのためのコードが実際に用意されていた。使われていないだけだった。

コードを読むと、理由はあっけないものだった。

ggml_backend_reg_t reg = ggml_backend_reg_by_name("CUDA");
if (!reg) { reg = ggml_backend_reg_by_name("ROCm"); }
if (!reg) { reg = ggml_backend_reg_by_name("MUSA"); }
if (!reg) { return false; }

GPUを動かすための仕組みには何種類かあって、それぞれ名前が付いている。NVIDIA向けの「CUDA」、AMD向けの「ROCm」、そして今回使っている「Vulkan」。上のコードは、CUDAかROCmかMUSAという名前を探しに行って、見つからなければ諦める。Vulkanという名前はどこにも書かれていない。

性能が足りないのではない。名簿に載っていないから呼ばれない。

だからこのモデルは、Vulkanで動かしているかぎり毎回必ずCPUに点数表を送り返す。62ミリ秒はそのぶんである。AMD向けの「ROCm」に乗り換えれば、この関数はそのまま動きはじめる。

とはいえ、それで消えるのは62ミリ秒と、点数表を送り返す10〜20ミリ秒。合わせて2割ほどだ。残る355ミリ秒は、どの仕組みに乗り換えても必要な計算である。半日かけて環境を作り直して2割、というのが正直な期待値になる。

1回あたりで戦うのは、ここまでだと思った。

回しても何も起きないツマミ

「entropy_boundが速度と品質の主ダイヤル」——手元のドキュメントにはそう書いてあった。私が以前そう書いた。

磨きをやめる判断に関わるパラメータはいくつかあって、これはその一つである。回してみた。既定値0.1を中心に、0.02から3.2まで160倍。

entropy_bound 長文: 磨きの回数 計算問題: 磨きの回数
0.02 21 10
0.05 25 9
0.1(既定) 20 26
0.4 17 8
1.6 17 10
3.2 17 9

増えたり減ったりしているだけで、傾向がない。出てくる文章の質も変わらない。160倍振って何も起きないというのは、そもそも別のものを動かしている証拠である。

停止条件のコードを読み直した。磨きが止まるのは、次の二つが同時に成り立ったときだった。

  • モデルの答えが前回と変わらなかった回数が、決めた回数に達した
  • 全体の迷いの平均が、決めたしきい値を下回った

entropy_boundはこのどちらにも出てこない。このパラメータが決めているのは「1回の磨きで何か所を確定させるか」だけで、いつやめるかには関わっていなかった。

では二つめの「迷いの平均のしきい値」が本命か。こちらも振ってみた。既定は0.005である。

しきい値 長文 計算問題
0.001 41 39
0.005(既定) 39 36
0.02 35 36
0.05〜1.0 35 35

0.02から上は完全に横ばいになった。しきい値を1.0にするというのは「迷いの平均がどんな値でも合格」という意味なので、この条件は事実上外れている。それでも回数は0.02のときと変わらない。

つまり磨きが止まっているのは、迷いが小さくなったからではなく、答えが変わらなくなったからだった。モデルは勝手に収束していて、既定値はその収束点にぴったり張り付いていた。二つのツマミには、はじめから余地がなかったことになる。

打ち切りではなく、火加減だった

残ったのはmax_stepsという、磨きの回数の上限を決めるパラメータである。既定は48。

上限を下げれば回数は減る。当然だ。ただしそれは途中で強制的に止めるということで、文章が途中で壊れるはずだ——と思いながらコードを見たら、そうではなかった。

t = t_min + (t_max - t_min) * cur_step / S      (S = max_steps)

磨きには「温度」という調整値があって、最初は高く、進むにつれて下がっていく。高いうちは大胆に書き換え、低くなるにつれて慎重になる。鉄を熱してから徐々に冷ますのに似ている。

このSがmax_stepsだった。つまりmax_stepsは「何回で打ち切るか」ではなく「何回かけて冷ますか」である。16に下げると、48回かけて下げていた温度を16回で下げきることになる。回数が減っても、冷まし方としては最後まで完結している。

512語を生成させて、時間と、出てきた文章の質を見た。

max_steps 長文: 回数 / 時間 計算問題: 回数 / 時間
4 4 / 1.8s 崩壊 4 / 1.8s 崩壊
6 6 / 2.7s 崩壊 12 / 5.6s ほぼ良好・正解
8 8 / 3.6s 崩れ 15 / 7.1s 良好・正解
12 24 / 11.3s 軽微な崩れ 17 / 7.7s 良好・正解
16 31 / 14.6s 良好 18 / 8.1s 良好・正解
24 38 / 17.5s 良好 19 / 8.6s 良好・正解
48(既定) 39 / 17.9s 良好 36 / 15.7s 良好・正解

16まで下げても文章は崩れなかった。速度は長文で1.2倍、計算問題で1.9倍になった。1回あたり435ミリ秒の壁と半日がかりの環境構築の話をしていたのに、設定を一つ変えるだけでこれである。

質の判定には少し工夫をした。「良い文章かどうか」を目で見て決めると、速くしたい気持ちのぶんだけ甘くなる。そこで、答えが一つに決まる問題を混ぜた。

電車が120kmを1.5時間で走り、その後60km/hで2時間走った。全体の平均速度は?

正解は240km ÷ 3.5時間で、およそ68.57km/h。これなら合っているか間違っているかを目で確かめられる。表の「正解」はその結果である。max_stepsが6以上なら、モデルはすべての条件でこれを正しく解いた。

崩れた文章には、崩れ方に特徴があった。max_stepsを8にしたときの長文はこうなる。

Core Innovation: Attention mechanism (re of RNNs/Lss). ** components:* Encoder Encoder,,,,--

語が重複し、途中で欠け、記号が散らばる。砂嵐を磨ききる前に冷めてしまった状態だ、と考えると納得がいく。

そしてもう一つ、危ない罠があった。上の表で、長文のmax_steps 4から8は時間が2秒前後しかない。速いのではない。512語作るはずが、途中で生成そのものが止まって256語しか出ていない。壊れた砂嵐の中にたまたま終端の記号が現れると、モデルはそこで書き終えたと判断してしまう。

毎秒何語という数字だけ見ていたら、この3行は「圧倒的に速い設定」に見える。実際には仕事を半分で投げ出しているだけである。

速くする作業の半分は、速くすることではない

一度に256語を作るのだから速いに違いない、と私は思っていた。実際には22回やり直していて、毎秒26語だった。

そこから先で私が触った数字を並べると、CPUの計算を減らす話は62ミリ秒しかなく、GPUの計算はほぼ限界で、環境を作り直しても2割で、主ダイヤルだと思っていたパラメータは160倍振っても何も起きなかった。効いたのは、温度の下げ方を48回から16回に変えることだけだった。

そして、そのどれも、測るまでは分からなかった。私は最初に「CPUが3割」という絵を描いていて、それは倍以上外れていた。10行の時計を入れる10分を惜しんでいたら、間違った場所を半日かけて掘っていたことになる。

残っている問いが一つある。ROCmに乗り換えれば2割は確実に取れて、そのうえでAMDの計算ライブラリがVulkanより速いかどうかは、まだ誰にも分からない。速ければもう一段、そうでなければ半日ぶんの授業料である。

これは、測ってみないと分からない。

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?