コンテンツにスキップ

Block による到達 — 設計

Status: 出荷済みで、2.6.0 から既定で on です。2.6.0a5 がこの段階を 2 つのゲート CPERSONA_BLOCK_BUILD_ENABLED と CPERSONA_BLOCK_RETRIEVAL_ENABLED の後ろで出荷し、 2.6.0a6 は Block ごとのベクトル (スキーマ v17) を加え、reconstruct が Block の腕の到達したものを 返すようにしました。2.6.0 は、第 7 節が述べる計測にもとづいて、両方のゲートを既定で on にしました。 見積もりと書いてあるものは見積もりであり、どれを実測で置き換えてから「完了」と呼ぶのかは 最後の節が述べます。

0. 欠陥と、この段階がそれに対してすること

埋め込み窓より長いレコードは、そのまま保存され、一度だけ埋め込まれます。その 1 本のベクトルは 窓が受け取った範囲から計算されるので、それより後ろの本文はベクトルの内容に一切寄与しません。 溢れ分の tree は既にそうしたレコードをノードへ分割して各ノードを埋め込んでいますが、それらの ノードは recall item が該当箇所を引用するときにしか読まれません。どの検索経路もノードを見て いません。帰結は正確に言えます — 長いレコードの末尾は、クエリにどれだけ近くても、意味検索 からは到達できません。

本プロジェクトの配備では、5,341 件のレコードのうち 2,419 件 (45.3%) が窓を超えており、 本文 4,242,156 字のうち 2,074,065 字 (48.9%) が窓の外にあります。

これは体積であって、何かを作る理由ではありません。しかもこの数字は、肝心の問いより先に 測られたものです。肝心の問いは「到達できない本文が何を犠牲にしているか」で、それには先行する 実測が答えています — 末尾を狙うクエリが、狙っている文書の語彙をそのまま使う場合、字句側と 融合側の腕は tree なしで既に hit@10 を 100% で返します。この場合、さらに遠くへ到達する利得は ゼロです。利得が出るのは、対象と語彙を共有しないパラフレーズの場合だけで、そこでは到達によって hit@10 が 9.6% から 34.3% へ動きました。同じ実測で、無関係な短いレコードが結果集合へ引き込まれる 巻き添えが 3.6〜4.6 ポイント、どの構成でも常時発生し、改善した例は 1 件もありませんでした。 したがって正味は P × 利得 − (1 − P) × 損失 であり、P は実クエリのうち「末尾をパラフレーズで 狙う」ものの割合です。P は測られていません。

この段階は、正味が正だとは主張しません。作るのは能力と計器です — 構造上届かなかった本文が 届くようになり、その到達の失敗が帰属可能になります。精度は独自の入場条件を持つ別の段階であり、 本ページはそれを代弁しません。

1. Block とは何か

Block は、レコード自身の本文の中の、節に相当する短い範囲であり、その本文への offset で 識別されます。内容の複製は持ちません。Block は派生物です — すべての Block を削除しても、 機能を無効にしたサーバーが返す答えは 1 つも変わらず、失われた Block 集合や壊れた Block 集合は 修復されるのではなく親から作り直されます。

次の 3 つの範囲は区別して保ちます。これらを混同することが、引用が「それを限定している条件」から 切り離される経路だからです。

範囲 基準 用途
Block span 原文中の短い節 identity と、ヒットの帰属先
表現の入力 モデルへ渡す固定範囲 ベクトルの生成
引用 context Block を含む有界な連続範囲 読み手に見せるもの

この段階では、表現の入力は Block 自身の本文です。引用 context は溢れ分の tree が既に提供して いるものであり、それを広げてもベクトルの再生成は要りません。

2. レコードを Block へ分ける方法

分割は決定論的で、モデルを使いません。構造境界 — 段落、文末、list item、table 行、code fence、 括弧、引用 — を検出し、述語をその主語・目的語・条件・否定から引き離さない境界でのみ分割します。

読点だけを理由に切ることはありません。policy が判断できない場合は大きい単位を保ちます — 分割されていない文は正しい Block ですが、否定の手前で切られた文はそうではありません。 強制的に分割されるのは窓を超える入力だけで、強制された境界はそれと分かる形で記録されます。

出来上がった span 列は、親の本文を gap なし・非重複で覆います。Block の境界はノードの境界に 従属します — Block はノードを跨がず、ノード境界が断ち切った節は、1 つの Block が 2 つのノードに またがることを許すスキーマではなく、引用 context によって補われます。

fixture が再現する形は、作り物の難しさではなく配備自身のコーパスから採ります — 条件の後の否定、 後の文での訂正、主語の省略、混在した文字種、code fence、長い URL、ノード境界をまたぐ節は、 いずれもそこに十分な量で実在します。ただし fixture の本文は写しではなく書き起こしです。 テストファイルは公開される成果物であり、コーパスはそうではないからです。コーパスは逆の側から policy を検定します — 全レコードに対して分割を実行し、その分割を assert する。§3 の数値は そこから出ています。

3. 表現は 1 次元あたり 1 ビット

各 Block のベクトルは各次元の符号へ量子化され、ビット列として保存されます。1,024 次元なら 1 Block あたり 128 バイトで、float32 の 4,096 バイトに対する比になります。

符号を使う動機はランダム超平面の同一性です — 2 つのベクトルをランダムに引かれた超平面が 分離する確率は θ/π であり、これが Hamming 距離を角度の不偏推定にします。しかしこの同一性は、 ここでの量子化器にはそのままでは適用できません。 ここでのビットはモデル自身の座標の符号であり、 座標軸はランダムな抽選ではありません。推定量の平均も、ビット間の独立性も、同一性からではなく 埋め込みの分布から論じる必要があります。本書はそれを行っていません。

したがって符号の忠実度はこの段階が確立していない発見的手法であり、設計はそれを隠すのではなく 前提にしています — §5 の予約が存在するのは、まさに Block ヒットの順位付けの質が未証明だからで、 予約は悪いヒットが与えうる損害を上から抑えます。これを決着させるのはコーパス自身の角度の幾何 (真の最近傍 Block への角度と、それ以外すべてへの角度の分布) を、全精度の Block ベクトルに対して 測ることです。その測定は存在せず、存在するまで符号幅を決める量は未知のままです。そしてその量は コーパスとともに増えます — 同じ偽生存予算・同じ保持率を保つには、広いコーパスほど細かい符号が 要るので、この配備に足りる幅は 1 桁大きい配備について何も述べません。

Block は float32 のベクトルを持ちません。 理由は好みではなく規模です。§2 の分割を配備自身の コーパス (その時点で 5,387 レコード) に対して実行しました — 107,428 Block、1 レコードあたり平均 19.9、 中央値 13 です。この倍率なら 100 万レコードのコーパスは 1,990 万 Block になります — ビット列なら 2.5 GB、float32 なら 82 GB です。後者は小さいマシンなら避けられる常駐量の話ではありません。 それはデータベースが、そしてそのファイル単位のバックアップのすべてが、保持しなければ ならない量です。

Block がビットの他に保持するのは 1 次元あたり 1 バイトで、Hamming の段が最上位に順位付けした 数百行を並べ替えるときにだけ読まれます (§4b)。同じ倍率なら 100 万レコードで 20 GB — float32 の 4 分の 1、ビット列の 8 倍で、メモリではなくディスク上に置かれます。それが何を 買うのか、なぜその費用に見合うのかは §4b が述べます。

この policy が作る Block は短く、それこそが policy の目的です — 中央値 45 字、90 パーセンタイルで 133 字、99 パーセンタイルで 285 字、そして境界が強制される 739 字の上限を超えるものは 1 つも ありません。その上限に達したのは 107,428 Block 中 42 回 (0.039%) なので、強制境界は日常では なく例外です。同じ実行で全レコードの分割も検定しました — 5,387 件すべてが gap なし・非重複で 覆われています。

走査費用も同じ算術に従います。基準機で 768 ビット × 100 万行の bitwise_count を単スレッドで 回すと 79.8 ms であり、配列実装の天井は約 190 万 Block の付近に来ます。実測倍率ではそれは 約 9 万 5,000 レコード — 現在の配備の 18 倍で、その配備の Block 索引は全体で 107,428 行、 走査は約 9 ms です。Block 索引は record 粒度の索引より 19.9 倍早くこの天井に達します — 行数がその倍だからです。天井より下では、本段階に新しい依存も第 2 の言語も要りません。天井より 上では、粗探索は索引アーキテクチャを所有する別のラインのものになります。ここでの構造は、 同じ走査が両方に使えるように選んであります。

これらの数値は、§2 の分割 policy を現時点のコーパスに当てた結果です。policy を変えれば数値も 動きますし、測り直しは十分に安い作業です。

4. 検索 — Hamming の候補を親へ畳む

Block の腕は、既存のレコードベクトル・字句・キーワードの各腕と並んで走ります。量子化した クエリとの Hamming 距離で Block を順位付け、有界な件数を取り、その結果を他の何かが見る前に 1 つのことをします。

Block のヒットは、候補になる前に親へ畳まれます。 複数の Block が一致したレコードは、 その最良の Block で代表される 1 件の候補であって、複数件ではありません。別々の Block の スコアを足すことは決してしません — それらは 1 つの原典に対する相関した観測であり、足せば レコードの長さが証拠へ変換されてしまいます。

走査する Block 数には上限があり、その上限は 1 本の長いレコードに食い尽くされない形で適用します。 各レコードが出せるのは本文の順で先頭から最大 64 個の Block まで (BLOCK_PER_PARENT_CAP) で、 その後に走査した集合を順位付けし、各レコードをその最良の Block で代表させます。全体の top-k を 切ってから重複排除すると、1 本の長文が pool を占有できてしまいます。

1 レコードの取り分は最良の Block ではなく先頭の Block です。それより後ろの Block は走査されない ので、非常に長いレコードの末尾の言い換えにはこの腕から届きません。各レコードの最良の Block を 選ぶには、上限を掛ける前に全 Block のビットを読んでレコードの中で順位付けする必要があり、 走査の費用が変わります。実運用の記憶を集めた非公開パック (4,478 件、1 件あたり平均 20.1 Block) では、64 Block を超えるレコードは 82 件 (1.8%)、取り分の外にある Block 行は 1.4% で、 300 問の根拠 389 箇所のうち 2 箇所がそこにあり、根拠がすべてそこにある問いは 1 問でした。

これらの上限はいずれも、呼び出し側が要求した件数から導出されません。件数だけを変えたとき、 走査された Block の集合も、候補 id の集合も変わってはなりません。

走査上限が効くときの切り落としは主キー順 — 行を読む順序であり、クエリに対しては任意の順序 — で起こります。この上限は走査が無制限になることを防ぐためのものであって、生き残った行が最良で あるという主張ではありません。上限自体は §3 が実測した配備の索引全体より上に置いてあります。

4b. Re-rank — 保存されたベクトルが、Hamming の段の最上位を並べ替える

§11 の到達の判定は、この腕の到達がどこで失敗するかを名指しました。到達できなかった対象の うち 3 分の 2 は、Hamming の段が最上位に順位付けした 200 レコードの中にありながら別枠を 逃していました — 段はそれらを見つけており、その順序が他のレコードを前に置いたのです。損失は 順序であり、順序こそ 1 ビットの符号が最も不得手なものです。

そこで、段が最上位に順位付けした行を、符号より多くを保持するベクトルで順位付けし直します。

  • 固定の深さ 200 行を、§4 の全順序 — 距離、次に種別・親 id・Block index — で取ります。 親へ畳む前の段階です。深さを行で切るのは、ベクトルを読めるのが行だからであり、これは §4 の 上限と同じくサーバー policy です — 呼び出し側の要求からは何も導出しません。
  • 各 Block は自分のベクトルを 1 次元あたり 1 バイトで保持します。 最大成分が 127 になるよう スケールして丸めます。スケール自体は保存しません。バイト列から計算されるのは cosine だけで、 cosine はそれを見ないからです。バイト列はビットを生んだのと同じ埋め込み呼び出しから得られ、 作るのに Block が既に行った以上の要求は要りません。
  • 行はクエリベクトルと保存バイト列の cosine で順序付けられ、その後 §4 とまったく同じように 親へ畳まれます — レコードはその最良の Block で代表され、何も足されません。cosine が同点なら 種別・親 id・Block index で解きます。どの Block も深さに入らなかったレコードは候補になりません。
  • 全部かゼロか。 行のどれか 1 つでもベクトルを持たなければ、そのクエリ全体が §4 の Hamming 順 — 直前のリリースが使っていた順序 — を使います。cosine と Hamming 距離は 1 つの順序に置けず、 混ぜれば一部の行を別の尺度で比較することになります。ベクトルを欠く Block 集合は current では なく (不変条件 8)、sweep がそれを作り直すので、この fallback は再構築の途中にある配備の状態で あって定常状態ではありません。
  • 応答は、どの順序が別枠を埋めたかを述べます — 予約された行の match_reason が order キーを持ち、その値は vector か hamming です。cosine 自体は示しません。§5 がスコアではなく距離を 示すのと同じ理由です。

測ったもの。 コーパスとクエリ族は到達の判定のために登録されたものです — 4,567 レコードを 92,807 Block に分割し、長いレコードの最初のノードより後ろを狙うパラフレーズ (対象 195 件) と 複製族 (189 件)。実装された recall 経路を通して、re-rank は予約の到達を 195 件中 57 → 78、 189 件中 64 → 81 へ引き上げ、Hamming 順が到達していた対象を 1 つも失いませんでした。腕を off に すると、応答はすべて直前のリリースと byte-identical でした。走査したすべての行をベクトルで re-rank すると 79 件と 81 件に達し、曲線は深さ 50 付近から平坦です。200 は、実行前に固定した グリッド上で、両方の族についてその値との差が 1 件以内に収まる最小の深さです。1 次元あたり 1 バイトは float32 と同じ対象に、誤差 1 件以内で到達しました。別枠は依然としてその多くが 対象でないレコードに取られています — 対象が占める別枠の割合は 13% から 18% に上がります。 これは到達であって精度ではありません。到達した本文が問いに答えているかどうかは reader を要し、 それを持つ計器はまだ作られていません。

ベクトルの出どころ。 4 つの出どころを同じ族の上で互いに比較しました。

出どころ 到達 (195 件中 / 189 件中) 費用
1 次元あたり 1 バイト、Block と共に保存 78 / 81 1 Block あたりディスク上 1,024 バイト、recall 1 回あたり 200 回の読み取り
Block の本文を、クエリ時に再度埋め込む 深さ 12 で 71 / 73 基準機で深さ 12 のとき 463 ms、200 のとき 5.6 s
親レコード自身のベクトル 21 以下 / — なし。ただし問われている末尾を見ることができない
Block を含むノードのベクトル 60 以下 / 54 以下 なし。ただし 10 倍の長さのノードの中で 1 つの節が薄まる

クエリ時の埋め込みは二重に除外されます。遅く、しかも決定論を壊します — 本プロジェクトが配備 する量子化された埋め込みバックエンドは、同じ本文に対して、要求に他に何が入っているかで異なる ベクトルを返します (単体で埋め込んだ本文と 16 件のバッチで埋め込んだ本文の cosine は中央値 0.980)。つまり順序が、どの候補がたまたま一緒に送られたかに依存してしまいます。

費用。 §3 の倍率なら、100 万レコードはこれらのベクトルを 20 GB 保持します。recall は そのうち 200 件を主キーで読みます — 基準機のページキャッシュから 2.9 ms、キャッシュを迂回する 200 回のランダムページ読み取りとして 12.5 ms です。腕全体としてはむしろ安くなりました — 中央値で 167 ms、re-rank なしの 193 ms に対してです。親への畳み込みが、走査したすべての行 ではなく re-rank 済みの 200 行に対して走るようになったからです。 ベクトルはデータベースの中の、専用のテーブルに置きます (§6)。データベースに置くのは、連続な ベクトル索引と違ってそこから作り直せず、全 Block を再び埋め込むほかないからで、ベクトルを 除いたバックアップは「再埋め込みが必要なコーパス」へ復元することになります。専用テーブルに 置くのは、Hamming の段が recall のたびにすべての Block 行を読むからで、128 バイトのビット列の 隣に 1 キロバイトを置けば、1 つも使わないページを約 8 倍読むことになります。

5. Admission — gate を変えるのではなく予約する

Block のヒットは、そうでなければ pool に入らないレコードを連れてくるために存在します — その親の全文ベクトルは、構造上、一致が悪いものです。それこそが末尾に到達できなかった理由の すべてです。したがって「そうした候補をどう通すか」という問いに、既存の品質 gate は答えられ ません。gate は親を採点し、その親こそが低く出るものだからです。

思いつきやすい 2 つの答えは、どちらも誤りです。親を自分のベクトルで判定させると、Block が たった今見つけたレコードが落ちるので、段階の目的そのものが消えます。Block 自身の類似度を gate へ流すのは、より分かりにくい形で悪い — 短い範囲は、長い範囲より特定のクエリに対して 高い類似度を出すので、同じ閾値が Block とレコードとで別のことを意味し、較正された動作点が 黙って別の母集団を支配します。相対的な量を絶対的な閾値と比較する gate は、本プロジェクトが 既に 1 度記録した欠陥です。

そこでこの段階は、Block が見つけた候補を 予約で通します — 結果集合の中の固定された少数の 別枠をそれらのために確保し、§4b が作る順序で埋め、その別枠については品質 gate を参照せず、他のどの位置 についても gate を変えません。帰結は、言い繕わずにそのまま述べます。

  • 予約は「悪い Block ヒットが与えうる損害」の上限です。Block のヒットが良いという主張では ありません。
  • 予約された候補は、gate が通したものを押し出しません。自分の別枠を占めるだけであり、予約が 埋まらなければ結果は水増しされずに短くなります。したがって機能を on にすることは行を 足すのであって、1 つも取り除きません — 直前のリリースが返していた答えは、すべてそのまま 返ります。
  • 予約の大きさは、保守的な既定値を持つ設定上の上限であって、調整されたパラメータではありません。 調整には reader を使った測定が要り、それは精度の段階に属します。

gate が較正されている量は動きません。新しいスコアが gate に届かないからです。この配置が存在 する目的は、その性質を保つことです。

呼び出し側にとっての帰結が 2 つあります。別枠は gate が埋めた位置に追加されるので、応答は 要求された件数を別枠の行数まで超えて行を運びます — 件数の内側から取れば、それは上の箇条書きが 禁じた押しのけになります。reconstruct は自分の候補の深さでこの recall を読み、同じ形を 保ちます: 予約されたレコードは窓の後ろに予約の印を付けた自分の項目として続き、窓の中の位置を 争いません (再構成窓)。他の行と一緒に 順位付けすれば、gate のスコアを持たないので必ず最後になり、窓が埋まっていれば届きませんでした。 そしてこの腕はローカルのベクトル検索が埋め込んだクエリベクトルで 順位付けするので、ベクトル検索が remote の配備では Block による到達は効きません。remote の サービスは自分で答えを返し、この腕が量子化するベクトルを作らないからです。

5b. 引用 — Block と、それを支配するもの

Block は節です。読むには十分に小さく、そしてレコードの逆を言うにも十分に小さい — 次の文が撤回しているなら「A を採用した」はレコードの言っていることではありません。 したがって読み手に見せるのは Block 単体ではなく、その Block を支配する親本文の連続範囲です。 範囲は 2 つの規則で決め、どちらも「自分に見えるもの」について保守的です。

  1. 文を終える。 文の先頭でない Block は後ろへ、文を終えていない Block は前へ伸ばします。 分割器は構造で切り、時に文の内側でも切るので Block は節になりえます。文を伴わずに読まれた節が、 引用が壊れる最初の形です。
  2. 限定語をたどる。 直前を限定する語で始まる Block は後ろへ、そのような語で始まる Block が 続く場合は前へ伸ばします。両方の文が完結していて、後者が前者を覆す場合がこれです。

どちらの規則も Block 単位で働き、どちらも発火しなくなるまで繰り返します。作られる文脈は固定の 上限で抑えられ、上限に阻まれてなお規則が伸ばそうとしているときは、その引用は不完全です — 完全な証拠として提示するのではなく、不完全であると報告し、代わりに読むべき範囲を渡します。 ペイロード予算が切り詰めた引用も同じ扱いです。

上限はサーバー policy であって予算ではありません。 文脈は何かが切られる前に決まるので、 予算を上げても item と抜粋が増えるだけで、引用が別のものへ置き換わることはありません — 応答が固定された列の接頭辞であるという性質は、これに依存しています。

これらの規則が覆わないのは、標識のない依存です — 内容だけで自らを告げる、次の文の訂正。 規則が見るのは標識のある依存と文境界であり、対照 fixture がそれを守らせます。独立した 2 文は 離して引用されなければならず、さもなければ「常に隣を取る」規則が、レコード全体を引用することで あらゆる分断テストを満たしてしまいます。

この形で返される範囲は、そのオフセットが測られた本文の revision を伴います。Block の範囲は 自分で自分を検証します (レコードの本文が変われば trigger がその Block を消すため)。素の span には それに相当するものが無く、古いオフセットを新しい本文に当てれば、レコードが一度も言っていないものを 引用することになります。書き換えられたレコードは提供されず、拒否されます。

6. スキーマと lifecycle

Block は、溢れ分の tree のノード表と同じ形の専用テーブルに保持します — 親の種別と id、 Block の index、開始と終了の offset、長さ上限が終端を強制したことを示すフラグ、ビット列、 そしてビットを生成したモデルの identity です。親の本文は複製しません。

§4b の re-rank ベクトルは、同じ 3 列をキーとする第 2 のテーブルに置き、他には何も持ちません。 ベクトルは自分の Block と同じトランザクションで書かれ、Block テーブルのトリガーが Block と 共にそのベクトルを削除します — したがって、削除・書き換え・agent による消去など、既にレコードの Block を落とすすべての経路が、そのことを教えられなくてもベクトルを落とします。Block テーブルの 軸のトリガーはこのテーブルには届きません。このテーブルは決して絞り込まれず、絞り込み済みの段が 既に選んだ行について、主キーでのみ読まれるからです。

トークン数と窓の列はありません。ノード表は両方持っていますが、分割器は設計上オフラインなので どちらにも正直な値を持てず、その性質を壊さなければ埋められない列は、壊すことへの誘いです。 Block の長さは終端から開始を引いたものです。

行はさらに、親から写した分離軸を持ちます。ノード表には要らなかったものです。Block はノードと 違って検索経路から読まれるので、コーパス全体を順位付けしてから絞る粗探索は、権威がその後 落とす行に top-k を使い切ってしまいます — bucket がコーパスの 1% なら、post-filter の後には ほとんど何も残りません。この写しは第 2 の権威ではありません。分離述語の出どころは 1 つだけで、 hydration が fail-closed で再適用します。ここでの義務は一方向です — この表が差し出す行は、 権威が認める行の superset でなければならない。

したがって retag は Block に届く必要がありますが、破壊してはいけません — 本文は変わって いないのでベクトルは有効であり、作り直せば同じビットに辿り着くために埋め込み呼び出しを 費やすだけです。軸のトリガーは写しを UPDATE し、本文のトリガーは集合を DELETE します。 この非対称は意図的なもので、その両側をテストが保持します。

構築は非同期で、tree が既に使っているキューの上で行います。Block 集合は、親が分割時の本文を まだ保持している場合にのみ書かれ、途中まで作られた集合が current 扱いされることはありません。 クラッシュは未公開の集合を未公開のまま残し、再開した run は自分が構築していた revision を 確認し直します。

失効は tree が確立した機構を再利用します — レコードの削除時、および content か summary が 実際に変化した時にノードを落とすデータベーストリガが、同じ文で Block も落とします。本 プロジェクトは外部キーを使っていないので、cascade は仮定するのではなく明示的に書きます。

各 Block 集合と共に保存されるモデル identity は、それを生成したものです。どのモデルが応答したかを 報告できない配備では、設定上の既定値がたまたま一致したことを根拠に Block を current と扱っては なりません — unknown は unknown であり、埋め込みサービスに自分の identity を述べさせるための 作業が別に存在します。

7. 2 つの gate と既定

2.6 の pre-release のあいだ、本機能は opt-in で、正味の利得が測られるまで既定への昇格は範囲外でした。 それを決める量は §0 が「測られていない」と述べたものだからです。2.6.0 では、その正味を P を経ずに 端から端まで測りました。実際のコーディングエージェントの記憶 (4,478 件) から作った非公開のパックで、 設定を選ぶために取り分けた 150 問に、モデルの読み手が各設定の返したものから答えました。recall では、 Block を読むすべての設定が、読まない同じ設定より多く答えました (rsf で 114 → 126、rrf で 115 → 122)。reconstruct では両者の差は 1 問以内でした。2.6.0 が持つ最良の設定は Block を読むもので、 2.6.0 は両方のゲートを既定で on にしました。パックは公開できないため、これは 1 つのストアの問いに対する 結果であり、公開データでの計測が続きます。

スイッチは 2 つに分けます。1 つにまとめると、使っていない側の費用を配備に払わせてしまうからです。

スイッチ 支配するもの off が意味すること
Block の構築 そもそも Block を作って保存するか 埋め込み呼び出しなし、行なし、キューの仕事なし
Block の検索 recall のときに Block の腕を走らせるか 索引は存在してよく、何もそれを読まない

構築を on にすると、既存コーパスに対する有界な backfill が始まります。sweep は同時に 1 つだけ 存在し、上限に達して止まるときは自分の継続をキューへ積みます。したがってコーパスは、再起動を またいだ有界な run の連なりとして構築され、コーパスがかかるだけキューを占有する 1 回の run には なりません。継続が運ぶ cursor はどこから再開するかを述べるだけで、そこにあるレコードが何と 書いてあったかは述べません — 集合が書かれるのは、親が分割元の本文をまだ保持している場合だけ なので、再開した run は自分が構築している revision を検査し直します。まだ何も構築していない run は、上限に達していても目の前のレコードを 1 つは始めます — さもなければ、単一の上限より大きな レコードがすべての run の停止点になり、sweep はそこを通過できません。

構築が off のまま検索を on にするのは設定エラーであり、サーバーは起動時にそれを報告します — 呼び出し側が期待する理由のある行数より少なく返る、静かな no-op にはしません。

両方 off のとき、サーバーは直前のリリースがしたことをし、かかっていた費用だけかかります。 Block テーブルが存在することで変わる経路はありません。

以下の上限はいずれもサーバー policy で固定され、何かを測る前に登録されるものであり、 呼び出し側の要求から導出されることは決してありません。

上限 支配するもの
走査 Block 上限 1 回の呼び出しが Block 索引をどれだけ走査してよいか
親あたり Block 上限 その上限を 1 レコードがどれだけ消費してよいか
re-rank の深さ 走査した行のうち何行を、保存されたベクトルで re-rank するか
別枠の行数 Block のヒットが埋めてよい別枠の行数
構築キューの上限 backfill 1 回あたりのレコード数・文字数・埋め込み呼び出し数・経過時間
Block の最大長 強制境界を取る位置

分量の上限がトークン数でなく文字数を数えるのは、§6 の表がトークン数を持たないのと同じ理由です。 分割器はオフラインであり、トークン報告を取得しなければ守れない上限は、「取得しない」という決定の 手前にネットワーク呼び出しを置くことになります。上限はレコードとレコードの間で検査され、 レコードの内側では検査されないので、run は自分が始めたレコードのぶんだけ超過します。

run が報告するものは、その run がしたことと、コーパスが保持しているものを分けて述べます。 構築したレコード数は coverage ではありません — 許された範囲をすべて構築した run も、コーパスの どれだけに Block があるかについてはそれ自体では何も述べず、backfill を見ている運用者が動きを 見たいのは後者の数だからです。

この数は health の発見事項でもあります。構築が on のとき、check_health は missing_blocks を報告します — 2 つ以上の Block に分割されるのに current な集合を持たない レコードであり、builder と sweep が使うのと同じ分割・同じ currentness 述語によってオフラインで 見つけられるので、3 者が 1 つのレコードについて食い違うことはありません。fix=true のときは 1 回の run で最大 50 件を構築し、埋め込みは書き込みロックの外で行い、レコードを書き換えることは 決してありません。これを必要とする場合が 2 つあります。タスクキューが off の配備には他に builder が ありません — 書き込み経路も sweep も、どちらもキューの上で走るからです。そして、Block 集合が re-rank ベクトルを持たないリリースから上げる配備では、すべての集合が current でないと 判定されます (不変条件 8)。sweep は有界な run でそれらを作り直し、この check は運用者がその 進捗を見て、前へ進めるための手段です。構築が off のとき、check は何も報告せず、何も 構築しません。

8. 不変条件

  1. Block は親を書き換えません。Block は派生物であり、レコードが原本です。
  2. 機能が無効なとき、応答は直前のリリースと byte-identical です。Block の行が存在していても 同じです。
  3. 決定論 — 同じ本文・ノード配置・分割 policy が同じ span 列を生みます。tokenizer の影響を 運ぶのはノード配置であって、分割器自身は tokenizer を呼ばず、ネットワークにも触れません。 Hamming 順の同点も、re-rank の cosine 順の同点も、書き下された全順序で解かれます。
  4. 被覆 — Block 集合は親の本文を gap なし・非重複で覆い、どの Block もノード境界を跨ぎません。
  5. 件数からの独立 — 上限も re-rank の深さも予約も走査集合も、応答件数から導出されません。 件数だけを変えたとき候補 id の集合は変わりません。
  6. 1 レコード 1 候補 — 1 つのレコードの複数の Block が複数の候補になることはなく、それらの スコアが足されることもありません。
  7. 新しいスコアは品質 gate に届きません。Block のヒットは予約で通るか、通らないかです。
  8. Block 集合が current であるのは、親の現在の本文から既知のモデルで構築され、その中の すべての Block が re-rank ベクトルを持つ場合だけです。報告されないモデル identity は 一致ではありません。
  9. 引用は決して切られません — 読み手に見せる span は、それを限定する連続 context を伴うか、 不完全であると報告されます。

各不変条件は、変異によって検出力が示されたテストが保持します — 上限を件数へ再結合する変異、 1 レコードの Block を足す変異、Block の類似度を gate へ押し込む変異、途中まで作られた集合を current と扱う変異、そしてベクトルを持たない行をベクトルを持つ行に混ぜて順序付ける変異の それぞれが、名前の付いた assert を赤くしなければなりません。

9. この段階が主張しないこと

より良い答えを主張しません。本プロジェクトが退行を測っているベンチマークはレコード粒度で 判定するので、より細かい引用はそこに現れようがなく、それを判定しうる reader を使った計器は まだ作られていません。この作業について公開の場で述べることは、測ったもの — 到達 — と、 精度は測っていないという事実の両方です。

末尾に到達する価値があるとは主張しません。P は未測定であり、先行する実測は「末尾をパラフレーズで 狙う」以外のすべてのクエリに常時の費用があることを示しました。この段階はその取引を可視に しますが、決着はつけません。

近似が無害だとは主張しません。Hamming による順位付けは近似であり、それが浮上させ損ねた候補は 後段では回復されません。

呼び出し側ができることを増やしません。ツールは追加せず、引数も変えません。予約された行の match_reason にキーが 1 つ増え、どの順序がその行を置いたかを述べます (§4b)。

10. 別途決めること

  • §4b で決着 — Block は float32 のベクトルを持たず、1 次元あたり 1 バイトを持ち、第 2 の段が それで Hamming の候補を再順位付けします。未決なのは、§5b の引用が同じベクトルで Block を 選ぶべきかどうか、そしてそうした場合にノードのベクトルに用途が残るかどうかです。
  • ノード自身を検索可能にするか。Block はノードより細かく同じ問いに答えるので、本段階を出荷 することでそれは「2 つの派生層のどちらを使うか」の選択になり、独立の問いではなくなります。
  • レコード層そのものを粗く走査するかどうか。本ページは腕を 1 本足すのであって、既存の腕を 作り直しません。厳密なレコード走査を近似で置き換えることは、呼び出し側が既に受け取っている 答えを変えることであり、予約された腕を足すのとは種類の違う変更です — 本ページには要らない 「劣化の上限」がそちらには要ります。これは §3 の配列の天井を超えた先の粗探索の実装と共に、 索引アーキテクチャを所有するラインのものです。ここで作る走査は、そのラインが作り直さずに 使える形にしてあります。
  • コーパス全体を見る腕に対して、走査窓が意味を持ち続けるかどうか。

11. この段階の判定

到達。 長いレコードの最初のノードより後ろに対象が位置するクエリの、登録されたスライス。 主張は「対象がベクトル経路を通って候補 pool に入る。以前は入れなかった」ことです。測定は 走らせる前に登録します — コーパス、選択規則、モデル、構成、判定則、標本数。変更後の数字が 証拠になるのは、同じ手順が変更前に反対向きの結果を出していた場合だけです。

非干渉。 固定コーパス上で、1 つではなく 2 つの状態を確認します — 両方のスイッチが off の 状態と、構築が on で検索が off の状態。後者こそ静かに失敗しうるものです。行が存在していて、 何かがそれを読んでしまうかもしれないからです。どちらも直前のリリースと byte-identical で なければなりません。

費用。 §3 の Block 数と索引サイズは見積もりでなく実測であり、その節はどの policy から出た 数値かを述べています。残っているのは、その規模での走査時間を算術ではなく基準機で実測すること、 そして backfill の費用と、中断された run からの再開です。

帰属を 1 件。 自分の到達がどう失敗するかを 1 つ名指しできるまで、この段階は終わりません — 届いたが順位が低すぎる候補か、切れて届いた引用か、pool を占有する長いレコードか。その名前が 精度の段階の入場条件であり、それ無しに精度の段階は始まりません。