土台が1台に痩せても誰も気づかなかった話と、正しいカードを渡しても8Bが数字を書き換えた話 ― 監視と知識の二本柱

結論から。今回は「基盤を守る」2つの仕組みを入れた。ひとつは監視——8B級モデルの推論土台が1台に痩せたら自分に通知が飛ぶようにした話。もうひとつは知識——8Bが平気で捏造する「この基盤だけの事実」を、正しい情報カードとして注入する仕組みだ。そして知識の方では、想定していなかった発見があった。8Bは「知らないこと」の捏造なら正しい情報で直せるが、「知っているつもりのこと」の上書きは、正解を目の前に置いても直らない。

1. 土台が1台に痩せたことに、気づいていなかった

結論: 汎用8Bの推論土台は2台構成のはずが、実は1台で走っている時間帯があった。監視がなかったから気づけなかった。

この基盤では、汎用的な質問への回答を担う「8B級モデルの土台」を2台で持つ設計にしている。常時起動の macbookair(M1)と、ベストエフォートの rtx3070ti だ。後者は普通に落ちる前提で、落ちても前者が受け止める——はずだった。

ところが別のセッションで Redis を覗いたとき、rtx3070ti の登録情報がそもそも無いことに気づいた。つまりその時間帯、汎用8Bの土台は macbookair 1台だけで走っていた。2台で冗長化したつもりが、片方が黙って落ちたまま、誰にも知らされずに単一障害点(SPOF)になっていた。

落ちること自体は設計内だ。問題は落ちたことに気づく手段が無かったこと。片肺飛行に入ったまま、もう片方まで落ちて初めて「全滅」として顕在化する——それが一番まずいシナリオだった。

設計: 通知するのは「状態が変わった瞬間」だけ

監視を足すとき、一番やってはいけないのは通知の垂れ流しだ。「土台が1台です」を1分おきに送り続けたら、誰も読まなくなる。そこで、通知するのは状態が遷移した瞬間だけと決めた。

  • 正常 → 劣化(2台 → 1台): 「片肺になった」と1回だけ通知
  • 劣化 → 全滅(1台 → 0台): 「土台が消えた」と1回だけ通知
  • 劣化/全滅 → 正常(回復): 「戻った」と1回だけ通知

継続中は黙る。同じ状態が続いている間は再通知しない。状態は Redis に記録し、前回と今回を比べて食い違ったときだけ送る。この「前回状態との比較」を挟むことで、Coordinator を再起動しても同じ通知が蒸し返されない。

「何台以上なら健全か」「どのノードのどのモデルを土台と見なすか」は設定値で外に出した。将来ノード構成を変えても、コードには触らず設定だけで追従できる。

実機で、劣化と回復の両方を出させてみた

実装したら、わざと壊して確かめる。rtx3070ti のワーカーを一時停止すると、心拍(ハートビート)の有効期限が切れて登録が消える。次の監視周期でそれが検出され、通知アプリ(Nextcloud Talk)に着弾した。

⚠️ 8Bフロア劣化(SPOF): 汎用8B worker 1/2台
稼働: macbookair / 欠落: rtx3070ti

そして rtx3070ti を復帰させると、今度は回復の通知が来た。

✅ 8Bフロア回復: 汎用8B worker 2/2台
稼働: macbookair, rtx3070ti / 欠落: なし

劣化・回復の1往復が、状態遷移のたびに1通ずつ、継続中は無音で流れることを確認できた。正直に書いておくと、「全滅」(0台)の通知だけは実機で出していない。両ノードを同時に止める必要があり、運用上そこまでやる踏ん切りがつかなかった。ロジック上は劣化・回復と同じ経路を通るので動くはずだが、「動くはず」と「動いた」は別物だ、というのは今回のもう半分のテーマでもある。

2. 8Bは「知らないこと」だけでなく「知っているつもりのこと」も間違える

結論: この基盤だけの事実を正しく答えさせるため、情報カードを注入する仕組みを作った。すると、捏造は消えたのに「一般常識との混同」だけが残るという、狙い撃ちで特定できる形の誤りが見つかった。

以前から分かっていた問題がある。8B級モデルは、一般的な知識はそこそこ答えるが、この基盤に固有の事実になると自信満々で捏造する。たとえば「Coordinatorのキュー命名規則は?」と聞くと、実在しないメッセージキュー製品の名前を混ぜた、それらしい嘘を返してくる。学習データに入っていない情報だから当然だ。

まず「検索スコアで足切り」を試して、捨てた

素直な発想は「関連する社内文書を検索して、スコアが高ければ注入、低ければ無視」だった。だが実際に測ると、この足切りは機能しなかった。関連文書と無関係な文書のスコアが重なってしまい、しきい値で線を引けない。原因は、長大な引き継ぎ文書を一定の文字数でぶつ切りにしていたことで、ベクトルが密集して「近い/遠い」の解像度を失っていた。

そこで方針を変えた。文書を切り刻むのではなく、1つの事実につき1枚のカードを手で書く。結論を先頭に、200〜400字で。これを専用の置き場(別コレクション)に、分割せず1枚=1点として入れる。同じ検索スコアの分離テストをかけ直すと、今度はきれいに分かれた。関連質問は高く、無関係質問は低く着地し、しきい値で線が引けた。「検索して注入する価値はあるが、注入の可否をスコアで判定するには、そもそもデータの持ち方が悪かった」という切り分けだった。

山場: カードを渡しても、8Bは数字を書き換えた

カードを前置きして8Bに答えさせる比較実験をした。捏造は見事に消えた。実在しないAPI名も、架空のキュー製品名も、カードを渡した途端に出てこなくなった。ここまでは想定どおり。

ところが1枚だけ、奇妙なことが起きた。PostgreSQL の運用に関するカードに、ある設定値の既定が「2億」だと明記してある。カードを渡した状態で8Bに答えさせると、8Bはその数字を「20億」に書き換えて回答した。カードには正しく2億と書いてあるのに、だ。

これは推測だが、8Bの中には「この種の上限値は20億くらい」という近い一般知識がうっすら入っている(実際、関連する物理上限は約21億だ)。カードの2億と、記憶の20億がぶつかったとき、8Bは目の前のカードではなく自分の記憶を優先した

対照的に、この基盤にしか存在しない事実——競合する一般知識が8Bの中に一切無いカード——は、一字一句カードどおりに答えた。つまり上書きは、8Bが「知っているつもり」の領域でだけ、狙い撃ちで起きる。

これは以前の実験で見た現象の、技術版だった。あのときは早口言葉の「二羽」を、正解を何度も教えても8Bが受け付けなかった。今回は数字。知らないことの捏造は、正しい情報を渡せば直る。だが知っている(つもりの)ことの上書きは、正解を目の前に置いても残る。

直し方: 「記憶より、渡された一次情報を信じろ」と命じる

対策はカードの渡し方に一文を足すことだった。要旨はこうだ——「数値・名称・コマンドは正確な一次情報である。自分の記憶と食い違う場合は、必ず参考情報の値をそのまま使え」。この指示を添えると、8Bは2億を保持するようになった。記憶と衝突したときに、どちらを優先するかを明示的に指定してやる必要があった。

仕上げに、この仕組みを本番のルーティングに組み込んだ。固有名詞を含む質問が来たら、まずカードを検索し、しきい値を超えたら回答本文に前置きして通常の8Bへ——という経路だ。既定はオフで、既存の動作には一切影響しない。実際に「キュー命名規則」を聞くと、カードが注入され、8Bが正しい命名規則を、捏造ゼロで答えた。以前は同じ質問に架空の製品名を返していたモデルが、だ。

3. おまけ: 正しいカードを作るのも、8Bと同じ罠を踏む

カードは増やしていく前提だ。今回、運用系の事実を4トピック追加しようとして、自分自身が原則違反をやらかした。

「PostgreSQLへの接続方法」と「回答が入るテーブルのカラム名」——この2つを、1枚のカードに詰め込んで書いた。1事実1カードの原則を、自分で破っていた。分離テストをかけると、たちまち崩れた。カラム名を問う質問だけが沈み、しきい値を割った。

面白かったのは、テストがちゃんとそれを検出したことだ。1枚のカードに2つの事実を混ぜると、ベクトルが片方の事実(接続方法)に引っ張られ、もう片方(カラム名)の質問との距離が開く。原則を破った瞬間に、計測がそれを数字で突きつけてきた。カードを2枚に割って入れ直すと、沈んでいた質問は0.47から0.76へ跳ね上がり、分離は回復した。

誤ったカードは、捏造よりたちが悪い。「記憶より信じろ」と命じて注入する一次情報だから、間違ったカードを入れたら8Bはそれを忠実に間違える。だからカードは、書いたら必ず計測する。今回の一件は、その規律が実際に効くことの実演になった。

まとめ

監視の話と知識の話は、別々のことのようでいて、根は同じだった。「そう設定した/そう渡した」と「実際にそうなっている/そう受け取られた」は別物だという一点だ。土台は2台のつもりで1台だったし、正しいカードを渡したつもりで8Bは記憶を優先していた。どちらも、通知を出させ、スコアを測り、実際のログと回答を見るまでは気づけなかった。派手な障害ではないが、こういう「静かなズレ」を見えるようにすることが、小さなモデルを実用の水準まで引き上げる地道な作業なのだと思う。