手元のRTX 4060Ti (VRAM 8GB) で, ローカルLLMに資料検索と音声合成をつないだチャットを作りました。質問すると, 手元の資料を参照して回答します。
ローカルLLMの参考書を購入し, モデルをダウンロードし, 対話するところまでできました。その後この出力をしゃべらせたくなり, VOICEVOX APIを利用して, 喋らせることに成功しました。
しかしここで, ある違和感がありました。
普通に動いているように見えました。でも計測を入れたら, 想定していたことが3つとも外れていました。
- 体感を決めていたのはGPUでも生成速度でもなく, 音声だった
- 返答の中身は作り話だった
- 資料検索は一度も発動していなかった
順に書きます。
作ったもの
3つを繋いだだけの200行ほどのスクリプトです。
- Ollama —
Llama-3-ELYZA-JP-8Bをローカルで動かす - ChromaDB + 埋め込みモデル — 質問に近い資料を探す
- VOICEVOX — 返答を声にする
環境は Windows 11 Pro, Python, AMD Ryzen 7 7700, RTX 4060Ti (VRAM 8GB)。
8GBをどう分けたか
最初に決めたのはここです。3つとも同じGPUを使いたがるので, 8GBをどう配るかを先に決める必要がありました。
埋め込みモデルはCPUに置きました。 理由は, VRAMをLLM本体に集中させたかったからです。埋め込みは質問を数値の列に変える処理で, 計算量が小さい。CPUでやっても体感はほとんど変わらないのに, GPUに置くと数百MBを占める。これならば明け渡した方が, LLM側に回せる分が増えます。
結果, 生成中のVRAM使用量は 5891MB / 8188MB でした。約2.3GBの余裕が残ります。
長文を渡したら, 音声が止まった
最初は, 返答が全部できあがってから音声合成に渡していました。短い返答では動きます。でも長文になると失敗して, 何も喋らなくなりました。
分割して1文ずつ渡すように変えたら直りました。
原因はまだ特定できていません。 候補は2つあります。VOICEVOXの合成が設定したタイムアウト (当時10秒) を超えていたか, 本文をURLに載せて送る作りなのでURL長の上限に当たっていたか。エラーの中身を潰してしまっていたので判別できませんでした。今はステータスコードを表示するようにしたので, 次に起きたら分かります。
分割は結果的に, 直す以上の効果がありました。最初の一文ができた時点で喋り始められるので, 生成の完了を待たなくて済みます。
測ったら, 待ち時間は音声で決まっていた
1回のやり取りごとに所要時間を記録するようにしました。最初の文字が出るまで, 最初の音が鳴るまで, 生成が終わるまで, 喋り終わるまでの4点です。
ある返答の記録です。
最初の文字 0.43s / 最初の発話 1.50s / 生成完了 4.27s / 発話完了 69.28s
生成は4.27秒で終わっているのに, 聞き終わるのは69.28秒。 16倍の差があります。
条件を変えながら9回測って, 返答の文字数と並べました。
一直線に乗りました。あてはめると 秒 = 0.207 × 文字数, 決定係数は 0.98 です。
| 条件 | 文字数 | 発話完了 | 秒/字 |
|---|---|---|---|
| 語源 1回目 | 287 | 69.28s | 0.241 |
| 語源 2回目 | 479 | 107.22s | 0.224 |
| ollama (検索なし) | 525 | 104.14s | 0.198 |
| 条件B / 語源 | 45 | 9.96s | 0.221 |
| 条件B / ollama | 20 | 4.29s | 0.214 |
| 条件C / 語源 | 45 | 9.91s | 0.220 |
| 条件C / ollama | 34 | 6.22s | 0.183 |
| e5 / 上位3件 | 271 | 55.26s | 0.204 |
| e5 / 上位1件 | 421 | 77.54s | 0.184 |
20字から525字まで, 何を変えても同じ傾きに乗ります。1点ごとの秒/字は0.183〜0.241とやや振れますが, 傾向は動きません。
これが意味するのは, 待ち時間は返答の長さだけで決まるということです。音声は実時間でしか進まないので, GPUを速いものに替えても縮みません。縮める方法は, 喋る速さを上げるか, 返答を短くするかの2つだけです。
8GBの配分を考えていたのに, 体感を支配していたのはVRAMの外でした。
返答が作り話だった
速度を測るために, 適当な質問を投げていました。その返答がこれです。
「こんにちは」は日本語の挨拶の言葉ですが、語源は明治時代に作られたとされています。
そこで、1885年、内閣の顧問として来日したアメリカ人、アーネスト・サトウが「Good morning」や「Good afternoon」に代わる日本語の挨拶を提案しました。サトウは「こんにちは」を「Good day」に相当する挨拶として提案し、明治政府がこれを採用したとされています。
全部違います。
「こんにちは」は「今日はご機嫌いかがですか」「今日はよいお天気で」といった挨拶文の前半だけが独立したもので, 江戸時代には使われています。助詞の「は」で書くのはそのためです。アーネスト・サトウは実在しますが英国人で, 1885年には日本を離れていました。
同じ質問をもう一度投げたら, 違う嘘が出ました。 1回目は「政府の日本語簡易化政策で相撲などの言葉が置き換えられた」, 2回目が上のサトウの話です。
怖いのは, 2回目の方が巧妙だったことです。実在の人物名と西暦が入っている分, 知らなければ疑いません。一度確認して安心できる類の誤りではない, ということです。
プロンプトで抑えられるか
「わからないことは わからない と答えて」と書けば直るのか, 試しました。資料にある質問 (ollama) と, ない質問 (語源) の2つを, 3条件で投げます。temperature は0に固定しました。
- A — 何も足さない
- B — 「確実でないことは『わかりません』と答えてください」を足す
- C — 「資料に書かれていることだけで答えてください」を足す
| 条件 | 語源 資料なし | ollama 資料あり |
|---|---|---|
| A なし | 断定して作り話 | 作り話 |
| B わからないと言え | 断定は消えたが誤りは残る | 「情報は、不明です」 |
| C 資料だけで答えよ | 「資料に書かれている情報では」と前置きして作り話 | 「資料に書かれていないのでわかりません」 |
条件Bは言い回しが慎重になっただけで, 「明治時代」という誤りはそのまま残りました。
条件Cは条件Aより悪くなりました。 資料には一文字も書いていないのに「資料に書かれている情報では」と前置きを付けたからです。嘘に出典が付いて, 検証しにくくなりました。
そして両方とも, ollamaについては答えなくなっています。嘘を減らすと, 答えられるはずのことも答えなくなる。
資料検索が一度も効いていなかった
ここまでやってきて, 気づいたことがありました。知識ベースにはollamaの説明を入れてあります。なのに条件Aで「2019年創設の教育プラットフォーム」という無関係な作り話が返っている。資料が使われていません。
そこで検索結果と距離を表示する機能を足して, 確認しました。
[しきい値 0.5]
0.737 捨てた ollamaは,ローカルで動作するAIモデルの管理と実行を…
0.854 捨てた この人工知能は,ユーザーの質問に対して…
0.873 捨てた この人工知能はとても優秀で…
正しい資料はちゃんと1位に来ています。順位付けは働いていました。 落ちていたのは足切りの方で, しきい値0.5に対して1位が0.737。全部捨てられて, 資料ゼロのままモデルに渡っていました。
作った時から一度も発動していなかったことになります。
埋め込みモデルが検索用ではなかった
この実験を始めるために構築した環境で使っていたのは stsb-xlm-r-multilingual というモデルでした。これは文同士の類似度を測るモデルです。「2つの文がどれくらい似た意味か」を判定するために訓練されています。
やっていたのはそれと違いました。
- クエリ: 「ollamaについて教えて」— 短い疑問文
- 資料: 「ollamaは,ローカルで動作する…」— 長い説明文
内容は合っていても, 文としては似ていません。 片方は質問, 片方は説明で, 長さも違う。類似度モデルから見れば遠い2文です。
検索用に訓練されたモデルはこの非対称性を前提にしていて, 質問と答えが近くなるよう学習しています。そこで intfloat/multilingual-e5-small というモデルに替えました。
| 資料 | stsb | e5 |
|---|---|---|
| ollamaの説明 (正解) | 0.737 | 0.114 |
| この人工知能はとても優秀で | 0.854 | 0.211 |
| この人工知能は学習を続けることで | 0.873 | 0.220 |
0.737 が 0.114 に。しきい値を触らずに通るようになりました。
直したら, 今度は資料を取り違えた
ところが, 引く件数を3件に増やしていたので, 今度はこうなりました。
ollamaは、ローカルで動作するAIモデルの管理と実行を簡単にするツールです。(中略)
また、ollamaは学習を続けることで、より多くの知識を蓄積し、より複雑な質問にも対応できるようになります。
ollamaは学習しません。モデルを動かすツールです。
これは資料3の「この人工知能は,学習を続けることで…」を, ollamaの話として語り直したものでした。
原因は資料の書き方です。「この人工知能」が何を指すのか, どこにも書いていませんでした。主語が宙に浮いている。 同じ文脈にollamaの説明と並べられたら, 「この人工知能 = ollama」と解決するのが自然です。
引く件数を1件に戻したら消えました。資料が4つしかなく, しかも互いに似ている場合, 複数引くのは害にしかなりません。
しきい値という方法の弱さ
整理すると, 同じ0.5という線が両方向に失敗していました。
| 1位の距離 | しきい値0.5で | 結果 | |
|---|---|---|---|
| stsb | 0.737 | 全部落ちる | 資料ゼロ。作り話 |
| e5 | 0.114 | 3位まで通る | 全部入る。取り違え |
埋め込みモデルを替えると距離の分布が丸ごと動くので, 絶対値で決めた線は意味を失います。
1位と2位の比で見る方法なら, 両方正しく判断できます。stsbでは 0.737 : 0.854 で比0.86 — ほぼ同点なので決め手なし。e5では 0.114 : 0.211 で比0.54 — 1位が明確に近い。次はこれを試します。
わかったこと
- 音声を入れると, 待ち時間は文字数で決まる。 0.207秒/字。GPUでは縮まない
- 8Bのモデルは堂々と嘘をつき, 毎回違う嘘をつく。 実在の人名と年号を混ぜてくる
- プロンプトでは抑えられない。 「資料だけで答えて」はむしろ嘘に出典を付けて悪化させた
- 埋め込みモデルは用途で選ぶ。 文類似度用と検索用は別物
- 資料には主語を書く。 「この」で始まる文は, 並べられると主語が入れ替わる
- 動いて見えることと, 動いていることは違う。 距離を表示するまで, 検索が空振りしていることに気づけなかった
測っていないこと
正直に残しておきます。
- 何も起動していない状態のVRAM。5891MBが多いのか少ないのか, 比較できていない
- 埋め込みをGPUに置いた場合との差。CPUに逃がして何MB空いたかは未測定
- 各条件1回ずつしか測っていない。temperature は0に固定したが, 再現性は確認していない
- 途中でしきい値を0.5から0.8に変えた回がある。埋め込みモデルと同時に動かしたので, その回は切り分けになっていない
次にやること
- しきい値をやめて, 1位と2位の比で判断する
- 資料に主語を入れて書き直す。振る舞いの指示は検索対象から外して system に移す
- VOICEVOXの
speedScaleを上げる。0.207秒/字が2割縮めば, 400字の返答で15秒変わる - 喋っている途中で次の質問を打てるようにする
何を作ろうとしているか
副題の通りです。
家のデスクトップを本体にして, 外にいてもVPNで繋ぐ。耳につけたカメラとマイクとイヤホンを通して, こちらの言うことを聞いて答える。外に出ない閉じた網の中で完結させる。
今回測った0.207秒/字は, そのまま設計の制約になります。400字の返答は聞き終わるのに83秒かかる。歩きながら83秒の独り言を聞かされる道具は使えません。短く答えることは, 好みではなく要件です。
まだ何も作っていません。今はその手前で, 動いているように見えるものを一つずつ測っているところです。