手元のRTX 4060Ti (VRAM 8GB) で, ローカルLLMに資料検索と音声合成をつないだチャットを作りました。質問すると, 手元の資料を参照して回答します。

ローカルLLMの参考書を購入し, モデルをダウンロードし, 対話するところまでできました。その後この出力をしゃべらせたくなり, VOICEVOX APIを利用して, 喋らせることに成功しました。

しかしここで, ある違和感がありました。

普通に動いているように見えました。でも計測を入れたら, 想定していたことが3つとも外れていました。

順に書きます。

作ったもの

3つを繋いだだけの200行ほどのスクリプトです。

環境は 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回測って, 返答の文字数と並べました。

20字 / 4.3秒525字 / 104.1秒01002003004005000306090120返答の文字数秒
返答の文字数と, 質問してから聞き終わるまでの秒数。破線は原点を通る直線のあてはめ。

一直線に乗りました。あてはめると 秒 = 0.207 × 文字数, 決定係数は 0.98 です。

条件文字数発話完了秒/字
語源 1回目28769.28s0.241
語源 2回目479107.22s0.224
ollama (検索なし)525104.14s0.198
条件B / 語源459.96s0.221
条件B / ollama204.29s0.214
条件C / 語源459.91s0.220
条件C / ollama346.22s0.183
e5 / 上位3件27155.26s0.204
e5 / 上位1件42177.54s0.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に固定しました。

条件語源
資料なし
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つの文がどれくらい似た意味か」を判定するために訓練されています。

やっていたのはそれと違いました。

内容は合っていても, 文としては似ていません。 片方は質問, 片方は説明で, 長さも違う。類似度モデルから見れば遠い2文です。

検索用に訓練されたモデルはこの非対称性を前提にしていて, 質問と答えが近くなるよう学習しています。そこで intfloat/multilingual-e5-small というモデルに替えました。

資料stsbe5
ollamaの説明 (正解)0.7370.114
この人工知能はとても優秀で0.8540.211
この人工知能は学習を続けることで0.8730.220

0.737 が 0.114 に。しきい値を触らずに通るようになりました。

直したら, 今度は資料を取り違えた

ところが, 引く件数を3件に増やしていたので, 今度はこうなりました。

ollamaは、ローカルで動作するAIモデルの管理と実行を簡単にするツールです。(中略)

また、ollamaは学習を続けることで、より多くの知識を蓄積し、より複雑な質問にも対応できるようになります。

ollamaは学習しません。モデルを動かすツールです。

これは資料3の「この人工知能は,学習を続けることで…」を, ollamaの話として語り直したものでした。

原因は資料の書き方です。「この人工知能」が何を指すのか, どこにも書いていませんでした。主語が宙に浮いている。 同じ文脈にollamaの説明と並べられたら, 「この人工知能 = ollama」と解決するのが自然です。

引く件数を1件に戻したら消えました。資料が4つしかなく, しかも互いに似ている場合, 複数引くのは害にしかなりません。

しきい値という方法の弱さ

整理すると, 同じ0.5という線が両方向に失敗していました。

1位の距離しきい値0.5で結果
stsb0.737全部落ちる資料ゼロ。作り話
e50.1143位まで通る全部入る。取り違え

埋め込みモデルを替えると距離の分布が丸ごと動くので, 絶対値で決めた線は意味を失います。

1位と2位の比で見る方法なら, 両方正しく判断できます。stsbでは 0.737 : 0.854 で比0.86 — ほぼ同点なので決め手なし。e5では 0.114 : 0.211 で比0.54 — 1位が明確に近い。次はこれを試します。

わかったこと

測っていないこと

正直に残しておきます。

次にやること

何を作ろうとしているか

副題の通りです。

家のデスクトップを本体にして, 外にいてもVPNで繋ぐ。耳につけたカメラとマイクとイヤホンを通して, こちらの言うことを聞いて答える。外に出ない閉じた網の中で完結させる。

今回測った0.207秒/字は, そのまま設計の制約になります。400字の返答は聞き終わるのに83秒かかる。歩きながら83秒の独り言を聞かされる道具は使えません。短く答えることは, 好みではなく要件です。

まだ何も作っていません。今はその手前で, 動いているように見えるものを一つずつ測っているところです。