Skip to content

nnue-train: 教師 pool relabel 用の順序保存 chunk loader と score sidecar writer - #409

Merged
SH11235 merged 3 commits into
mainfrom
feat/rescore-ordered-loader
Sep 5, 2026
Merged

SH11235 merged 3 commits into
mainfrom
feat/rescore-ordered-loader

Conversation

@SH11235

@SH11235 SH11235 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

概要

GPU リスコアツール (教師 pool PSV を学習済み NNUE の 1-node 静的評価でリラベルする) の土台となる host 側部品を crates/nnue-train/src/rescore.rs に追加する。リスコアの不変条件は「sidecar 行 i = 入力 record i」の行対応 (全行・原順序・無フィルタ) で、学習用 BucketedPrefetchedLoader (worker ≥ 2 で順序非決定 + score-drop skip + epoch wrap) は流用できないため、同じ channel ring / slot recycle パターンの上に順序保存の別実装を置く。

設計要点

  • OrderedPsvLoader: worker が共有 counter で chunk (= 固定 record 範囲) を claim し、各自 PsvFileLoader::new_range で読んで decode。消費側が seq 順に再整列する。chunk 内容は record 範囲だけで決まり、worker 数・スケジューリング非依存 (決定論)。slot pool の bounded back-pressure で再整列 buffer も有界
  • 無フィルタ・wrap なし: score-drop / clamp を適用せず、yield される実 record 数の合計は必ず入力残数に一致する (一致し得ない状態は error)
  • 端数 padding: 末尾 chunk は最終 real 行の複製で pad_multiple (GPU tiled kernel の b % 16 == 0 制約) へ切り上げ、n_real で truncate する契約
  • fail-closed 三段防御: ①worker のエラーは result channel の Err として本流で伝搬 (blocking recv 中の消費側を必ず起こす。エラー受領で channel を閉じ全 worker を join して loader を毒化 — 以降の呼び出しも同じエラー) ②worker 内 panic は catch_unwind でエラーに変換して同経路、catch 外の panic は join backstop で捕捉 ③全 worker 終了時に受領 chunk 数 == 期待 chunk 数を検証 — chunk 欠損が正常終了 (Ok(None)) に化けない
  • record 検証: PackedSfenValue::decode は checked ではなく壊れた record も局面に化けるため、validate_board (玉の存在/一致、駒総数 ≤ 40) で検出可能な破損を硬いエラーにする。完全な合法性検証はせず「入力は教師生成パイプラインの正当な PSV」を API 契約として明記
  • ScoreSidecarWriter: i16 sidecar の追記 + .in-progress / .done marker 管理 (fingerprint text 一致時のみ件数ベース resume、奇数サイズ拒否、完了時に全件数検証して .done へ atomic 昇格)。fingerprint の組み立ては後続の rescore driver 側の契約

テスト (17 本、CPU-only — local-ci の CPU テストで完結)

  • loader: 順序保存 + 全行 yield (逐次参照と一致) / 端数 padding / padding 不要ケース / resume 開始位置 / 決定論 (2 run 一致) / torn file・start 超過の spawn 拒否 / 入力縮小の fail-closed / worker 4 + エラーで timeout 内に Err 停止 (deadlock regression) / panic のエラー伝搬 + 毒化継続 / 破損 record の拒否
  • writer: 書込→昇格→完了 skip / 件数 resume の bit 一致 / fingerprint 不一致 truncate / 奇数サイズ・超過拒否 / 件数不足で昇格拒否 (marker 温存) / 陳腐化 .done の作り直し
  • 通し: loader + writer の中断 → 再開 sidecar が一気通貫実行と bit 一致

検証

  • bash scripts/local-ci.sh フル PASS (sh11235 / ubuntu-3080ti、全 step)
  • Windows 側 fmt / clippy -D warnings / CPU テスト green (nnue-train 158 passed)
  • 独立レビュー (Codex): REQUEST_CHANGES (high 2 = エラー経路 deadlock・panic 無視、medium 1 = 破損 record 素通り、low 1) → 対応 → APPROVE (2 巡目所見なし)

後続: CLI 接続と GPU forward 接続 (rescore driver) は別 PR で行う。

🤖 Generated with Claude Code

SH11235 and others added 2 commits September 6, 2026 04:12
…r を追加

リスコアの不変条件「sidecar 行 i = 入力 record i」(全行・原順序・無フィルタ) を
満たす並列 decode を提供する。学習用 BucketedPrefetchedLoader は worker >= 2 で
順序非決定 + score-drop skip + epoch wrap のため流用できない。

- OrderedPsvLoader: worker が共有 counter で chunk (= record 範囲) を claim し、
  各自 PsvFileLoader::new_range で読んで decode。消費側が seq 順に再整列する。
  chunk 内容は record 範囲だけで決まり worker 数・スケジューリング非依存
  (決定論)。slot pool の bounded back-pressure と slot recycle は
  BucketedPrefetchedLoader のパターンを踏襲
- 末尾 chunk は最終 real 行の複製で pad_multiple (GPU tiled kernel の
  b % 16 == 0 制約) へ padding し、n_real で truncate する契約
- fail-closed: I/O エラー / short read (実行中の入力変化) / active-feature 超過で
  以降の chunk を 1 個も yield しない
- ScoreSidecarWriter: i16 sidecar の追記 + in-progress/done marker 管理
  (fingerprint text 一致時のみ件数ベース resume、奇数サイズ拒否、完了時に
  全件数検証して .done へ昇格)。fingerprint の組み立ては rescore driver 側の契約
- テスト 14 本 (CPU-only): 順序・全行 yield・端数 padding・resume 開始位置・
  決定論・torn file / 入力縮小の fail-closed、writer の resume bit 一致・
  fingerprint 不一致 truncate・完了 skip・奇数サイズ / 件数不足拒否、
  loader+writer 通しの中断→再開 bit 一致

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 検証を追加

- worker のエラーを side slot でなく result channel の Err として送る。side slot
  方式は blocking recv 中の消費側/他 worker を起こせず、欠損 seq 待ち + slot 枯渇
  で deadlock し得た。エラー受領時は channel を閉じて全 worker を join し、loader
  を毒化する (以降の next_chunk も同じエラー)
- worker 内 panic を catch_unwind でエラーに変換して同経路で伝搬し、channel 送信
  など catch 外の panic は join で捕捉する。さらに全 worker 終了時に受領 chunk 数
  = 期待 chunk 数を検証し、chunk 欠損が Ok(None) = 正常完了に化けないようにする
- decode 済み局面の安価な整合性検証 (validate_board: 玉の存在/一致、駒総数 <= 40)
  を追加。PackedSfenValue::decode は checked でなく壊れた record も局面に化ける
  ため、検出可能な破損は硬いエラーにする (完全な合法性検証は行わず、入力が正当な
  教師生成物であることを API 契約として明記)
- メモリ有界性コメントを実際の根拠 (in-flight chunk が slot を 1 個ずつ占有) に修正
- テスト追加: worker >= 2 のエラーで timeout 内に Err 停止 (deadlock regression)、
  panic のエラー伝搬 + 毒化継続、破損 record の fail-closed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-05T20:21:27.979529Z 061f95c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 061f95c738

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +698 to +701
for &score in scores {
self.out.write_all(&score.to_le_bytes())?;
}
self.written = new_total;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Poison the writer after a partial score write

When the underlying write fails after emitting part of this slice—for example, due to a transient filesystem or disk-full error—written remains unchanged even though the BufWriter or file may already contain a prefix. If the caller retries on the same writer, that prefix is appended again (or after a partial i16), and a later finish() can mark the corrupt sidecar complete because it checks only the logical counter. The writer should become unusable after any write error, or otherwise track and validate the physical output before promotion.

Useful? React with 👍 / 👎.

write が slice の一部を出力した後に失敗しても written カウンタは進まないため、
同一 writer で retry すると prefix の二重 append や record 途中への継ぎ足しが
起き、finish は論理カウンタしか見ないため破損 sidecar が .done へ昇格し得た。

- 任意の write エラーで writer を毒化し、以降の write_scores / finish は同じ
  エラーを返す (loader の poison と同じ思想)。復旧は次回起動の open (件数
  ベース resume、2 byte 境界で切れていない末尾は硬いエラー) が担う
- finish は flush + sync 後に物理ファイルサイズ == written × 2 を検証してから
  .done へ昇格する (論理カウンタと物理実体の突き合わせ)
- 書き込み先を SidecarSink trait (本番 File) で差し替え可能にし、部分書き込み
  失敗を注入するテスト (毒化後の retry / finish 拒否) と、外部混入による物理
  サイズ不一致で昇格が拒否されるテストを追加

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SH11235

SH11235 commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

P2 指摘 (部分書き込み後の writer 毒化欠如) を 21e9bcb で修正しました。

  • 毒化: 任意の write エラーで writer を毒化し、以降の write_scores / finish は同じエラーを返します (loader の poison と同じ思想)。部分書き込みされた slice の retry による prefix 二重 append / record 途中への継ぎ足しを構造的に排除します。復旧は次回起動の open (件数ベース resume。2 byte 境界で切れていない末尾は既存どおり硬いエラー) が担います。
  • 物理サイズ検証: finish は flush + sync 後に「物理ファイルサイズ == written × 2」を検証してから .done へ昇格します (論理カウンタと物理実体の突き合わせ。不一致は in-progress marker を残して硬いエラー)。
  • テスト: 書き込み先を SidecarSink trait (本番は File) で差し替え可能にし、①部分書き込み失敗の注入 → 毒化 → retry / finish 拒否 (sidecar_writer_poisons_after_partial_write_failure) ②外部混入による物理サイズ不一致で昇格拒否 (sidecar_writer_finish_rejects_physical_size_mismatch) を追加 (計 19 テスト)。

検証: Windows fmt / clippy -D warnings 0、sh11235 (ubuntu-3080ti) でフル local-ci.sh PASS。

@SH11235
SH11235 merged commit c2ac4ab into main Sep 5, 2026
1 check passed
@SH11235
SH11235 deleted the feat/rescore-ordered-loader branch September 5, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant