ベクトル走査窓の「到達範囲」と「新しさの優遇」¶
Status: 2.5.x 系で出荷済、既定は off。計測済みだが、計測は通らなかった。結果と 2.6 系の対応は 到達範囲・新しさ・far の票 を参照。本ページは機構の参照として残る。
SCHEMA_VERSION は 13 のまま、新しいランタイム依存も増えない。新設定が既定値の
あいだは、現行の走査が返す答えとビット単位で一致する (類似度が等しい行の並びを含む)。
1. ひとつの数字が、ふたつの仕事をしている¶
ローカルのベクトル検索は、最新 CPERSONA_MAX_MEMORIES 行 (既定 10,000。memories と
episodes をそれぞれこの幅で走査する) をコサイン類似度で順位付けする。この設定は費用の
上限として文書化されてきた。recall 1 回が読む行数を抑え、連続配置索引の大きさを決める
ためである。
六桁のコーパスでは、コーパスの大半をベクトル腕から見えなくもしている。素直な直し方は、 値を上げることに見える。
実際の recall 経路で測ると、上げることは緩和ではなかった
(benchmarks/measurements/results-scan-window-default-ab.md)。保存 237,654 文書で、
窓を 10,000 から 200,000 にした結果を示す。
| 層 | 意味 | Δ NDCG@10 |
|---|---|---|
| far — 答えが 10,000 行の窓より下にある | 広い窓のベクトル腕だけが見える | +4.93 ± 0.90 |
| near — 答えが 10,000 行の窓の中にある | どちらの窓でも見える | −20.19 ± 1.70 |
切り捨ては起きていない。どの腕のどの呼び出しもちょうど 10 行返し、品質ゲートの fallback は一度も発火しなかった。
near 層の損失は、reciprocal-rank fusion の中での順位の押し出しである。ベクトル腕が
融合に渡すのは上位 limit 行なので、10,000 候補の中で 3 位だった最近の答えが 200,000
候補の中で 30 位になると、ベクトルのリストの下のほうにいるのではなく、リストから
消える。reciprocal-rank の票が無くなる。字句の腕はまだその行を運ぶが、ベクトルの
票を失った行は、より良く融合した行に負ける。
つまり窓は、ふたつの仕事を同時にしていた。
- 到達範囲 (reach) — ベクトル腕がどこまで遡って見るか。設定名が指していた費用の 上限である。
- 新しさの優遇 (recency prior) — 最新の行だけを残すことで、最近の記憶はどれも コーパス全体ではなく 10,000 行の候補場で競えばよくなる。答えが最近にあるクエリに とって、これは無償の精度であり、この計器では 20 点ぶんの価値があった。名前も設定も 無く、費用上限の副作用として存在している。
窓を広げると、到達範囲を伸ばすその動作で優遇が消える。ひとつの数字が両方を制御して いるあいだ、どちらも単独では動かせない。本書はそれぞれに固有のノブを与える。
同じ測定の探索的な掃引が、答えが取ってはならない形も示している。窓 50,000 は 200,000 より多くの far 層の品質を買った (+12.87 対 +6.06)。far の答えは深さ 20,000〜29,500 に あり、広い窓はそれらを 150,000 行多く競わせただけだった。
答えより先へ到達しても、費用だけがかかって何も買えない。到達範囲をいくつにするにせよ、 near の場は小さいままでなければならない。
2. 採らない形: 段階つきの窓¶
最初の案は、幅がふたつある窓である。内側の幅の中の行は今日どおりに採点し、内側と 外側のあいだの行は低い重みで受け入れる。ふたつの幅が等しければ、今日の走査とビット 一致する。
これは採らない。重みはどこかから来なければならないからである。内側の幅を越えた行は、 コサインをそのまま持つか (その場合、内側の幅は何もしておらず、単一の広い窓である)、 コサインに年齢の関数を掛けるかのどちらかになる。
その関数は時間で重み付けしたスコアである。そして時間で重み付けしたスコアとは、2.6 系に 予定されている recency-weighted search そのものであって、そちらには固有の未決事項がある (confidence の再ソートとどう相互作用するか、時間的に一様でないクエリ分布で時間項に何の 価値があるか)。窓の設定に畳み込めば、それらを偶然で決めてしまう。
本設計が変えるのはどの行が候補になるかであって、候補の採点ではない。時間項も重みも 持たない。
3. 設計: ベクトルのリストをもう 1 本¶
設定はふたつで、うちひとつは既存のものである。
| 設定 | 意味 | 既定 |
|---|---|---|
CPERSONA_MAX_MEMORIES |
near 窓: ベクトル腕が今日どおりに順位付けする最新 N 行 | 10,000 (変更なし) |
CPERSONA_VECTOR_REACH |
到達範囲: ベクトル腕が合計で見てよい最新行数。走査位置 [N, REACH) の行が 2 本目のリストになる |
0 = 窓と同じ。far リストは存在しない |
REACH > N のとき、ベクトル retriever は 1 本ではなく 2 本の順位付きリストを作る。
- near リスト — 位置
[0, N)の中でコサイン上位limit行。今日作っているリスト そのものである。同じ行、同じ閾値、同じ安定な tie-break、同じ順序。 - far リスト — 位置
[N, REACH)の中でコサイン上位limit行。同じ切り方。
位置は走査自身の順序 created_at DESC, id ASC で数える。連続配置索引ファイルが書かれる
順序でもある。memories と episodes はそれぞれ同じ位置で分ける (今日の走査のされ方を
写す)。
融合は far リストを「順位付きリストがもう 1 本」として受け取る。reciprocal-rank fusion (出荷モード) では、変更はそれで全部である。既存の 3 つと同一のループが 4 つ目に増える だけである。
既存のリストはどれも触られないので、既存の行はどれも今日とまったく同じ票を保つ。far の 行は追加しかできない。新しさの優遇はこれで名前を持つものになった。near リストが幅 N で 存在すること、それ自体である。そして到達範囲は、もはや優遇を奪わない別の数字になった。
3.1 near リストが「似ている」ではなく「保存される」理由¶
融合が今日見る 3 本のリストは、ベクトルのリスト、episode FTS のリスト、memory keyword の
リストである。near リストは構成上、今日のベクトルのリストそのものである。走査位置
[0, N) は今日の走査が読む行であり、閾値と limit の切り方は同じコードで、tie-break は
前後とも走査順である。
行の reciprocal-rank の寄与は、その行の自分のリスト内の順位の関数なので、別のリストが 追加されても変わらない。far リストと near リストは位置で互いに素なので、両方に載る行は 無く、票が二重に数えられる行も無い。
この互いに素であることが、品質ゲートのスケールも正しく保つ。旧来のゲートは閾値を
「3 つの retriever が 1 行に与えうる最大スコア」3 / (K + 1) で伸縮する。far リストが
あっても、1 行が到達できる最大は依然 3 票である (near または far に加えて字句 2 本)。
したがって、この定数は行あたりの最大値のままである。較正済みゲートは生の融合スコアを
比較するが、同じ理由で影響を受けない。今日存在する行のスコア分布は動かないからである。
3.2 relative-score fusion¶
CPERSONA_RECALL_MODE=rsf では、各チャネルの生スコアをクエリごとに min-max 正規化し、
合計を有効チャネル数で割る。
far リストを 4 本目のチャネルとして融合すると、near の行の正規化値は保たれる (min と max は自分のリスト内で計算される) が、すべての行で除数が 3 から 4 になり、コサインスケールの ゲートに対して全融合スコアが下がる。代わりに far の行をベクトルチャネルへ併合すると、 near の行の min と max が変わる。
far リストが存在する時点でどちらもビット保存ではなく、下の測定は出荷モード rrf にのみ
登録されている。rsf では far リストは 4 本目のチャネルとして融合し、測定されるまで何も
主張しない。
3.3 ふたつの供給元¶
ベクトル走査には、同じ契約 (走査順の (ids, similarities)) に対する供給元がふたつあり、
どちらも far リストを同じ方法で作る。
- 連続配置索引:
select()はファイル順の位置を返す。near リストは今日どおりpositions[:N]、far リストはpositions[N:REACH]を取る。索引の watermark より上の行 (最後の build 以降に保存された行) は最新の行であり、near リストに属する。今日の走査に merge されるのとまったく同じである。 - SQLite 走査 (索引なし、または索引が辞退したとき): 同じ文に far 領域向けの
LIMIT ? OFFSET ?を付け、chunk で読む。ピークメモリは到達範囲でなく chunk で 抑えられる。OFFSETは飛ばす索引順を歩くが、それは索引なし経路が窓に対して既に払って いる値段である。到達範囲を大きくして遅くなりすぎるなら、それは索引を建てる理由で あって、この経路が近似する理由ではない。
どちらの供給元も far リストを作れなければならない。索引があるときだけ動く分離では、
索引の build が答えを変えることになり、索引の設計はそれを禁じている
(CONTIGUOUS_INDEX_DESIGN.md §2)。
4. この設定の費用¶
REACH > N のとき、recall 1 回は今日より REACH − N 行多く embedding を読む。
連続配置索引があれば、その読み出しは索引で測った速い経路 (SQLite 読み出しの 7 倍速) で
ある。無ければ、chunk 走査が上の測定で広い窓が払ったのとほぼ同じ費用を払う。237,654 行
に到達範囲 200,000 で p50 がおよそ 2 倍、どちらの場合も床は字句の腕である。
behavior-contracts.md のこの設定の項は、窓と同じ言葉でこの費用を述べている。
メモリは、走査経路では chunk サイズで、索引経路では索引ファイルで抑えられる。どちらも、 索引が既に持つ量を越えて到達範囲とともに増えることはない。
5. この設計がしないこと¶
- 時間で重み付けしない。よく融合した far の行はそのコサインで返り、near の行もその コサインで返る。far リストの票が near リストの票より軽くあるべきかは採点の問題で あり、下の測定がそれを提起する証拠になる。
- 値を選ばない。既定は 10,000 と off のままである。far リストが意図どおりに働くかは 事前登録した測定が決め、値はその後に、固有の文書を持つ固有の変更として選ぶ。
- episode penalty、confidence の再ソート、autocut、品質ゲートに触れない。どれも融合済み リストを従来どおり見る。よく融合した far の行のぶんだけ長くなっているだけである。
6. ゲート¶
設定の状態ごとに、ゲートをひとつずつ置く。
- 既定 (off): behaviour golden (
tests/golden/behaviour_252.json) と索引の等価性 テストが、差分ゼロを示さなければならない。設定が既定のあいだ、far リストは「走る コード」として存在してはならない。空を返す走査ではなく、guard であること。 -
on:
benchmarks/measurements/prereg-scan-window-reach-ab.md。どの腕も走らせる前に 登録し、広い窓を退けたのと同じ計器・同じ判定規則で測る。far 層は NDCG@10 で +5.0 以上 得て、near 層は 1.0 より多く失ってはならない。分離が通れば、到達範囲を上げることは値段を付けて文書化できる変更になる。near 層で 落ちたなら、等しい重みでは far リストの票が強すぎるということであり、次の候補は リストごとの重みになる。それは、この設計が意図的に recency-weighted の線へ残した 採点の問題である。