Skip to content

classic search: introduce SearchTree overlay and move n_in_flight out of Node#11

Draft
Copilot wants to merge 3 commits into
backprop3from
copilot/refactor-search-tree-overlay
Draft

classic search: introduce SearchTree overlay and move n_in_flight out of Node#11
Copilot wants to merge 3 commits into
backprop3from
copilot/refactor-search-tree-overlay

Conversation

Copilot AI commented Apr 28, 2026

Copy link
Copy Markdown

Node mixes persistent game-tree state with ephemeral search-local state (n_in_flight_, collision bookkeeping). This PR separates them across two commits without removing Node::parent_.

Commit 1 — Introduce SearchTree overlay

New src/search/classic/search_tree.{h,cc} provides a lightweight per-search overlay keyed by Node*. In this commit it takes ownership of shared_collisions_ (previously a raw std::vector on Search):

// SearchTree owns per-node search-local state and collision paths.
class SearchTree {
 public:
  void AddSharedCollision(std::vector<Node*> path, int multivisit);
  void CancelSharedCollisions();   // called at end of each batch
  // n_in_flight API used in commit 2 …
};

Search::shared_collisions_ is replaced by std::unique_ptr<SearchTree> search_tree_, initialised in the constructor. CancelSharedCollisions() and CollectCollisions() delegate to it.

Commit 2 — Move n_in_flight_ / virtual loss into SearchTree

  • Node: remove n_in_flight_ field and all derived APIs (GetNInFlight, GetNStarted, TryStartScoreUpdate, CancelScoreUpdate, IncrementNInFlight). FinalizeScoreUpdate no longer decrements n_in_flight. sizeof(Node) shrinks from 64 → 56 bytes.
  • SearchTree: gains the full virtual-loss API (TryStartScoreUpdate, CancelScoreUpdate, IncrementNInFlight, FinalizeScoreUpdate, GetNInFlight, GetNStarted).
  • search.cc: all call sites updated — selection (PickNodesToExtendTask), OOO collision handling (GatherMinibatch), backup (DoBackupUpdateSingleNode), verbose stats, and prefetch.
  • EdgeAndNode::GetU: now takes an explicit nstarted argument; callers obtain it via search_tree_->GetNStarted(node).
  • VisitedNode_Iterator: the n_in_flight-based early-exit optimisation is replaced by a simpler N==0 stop (functionally equivalent for the common case; iterator does not have SearchTree access).

Node::parent_, GetParent(), and GetOwnEdge() are intentionally preserved — ancestry reconstruction migration is deferred to a future step.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • gitlab.com
    • Triggering command: /home/REDACTED/.local/bin/meson meson setup /tmp/lc0build --wipe -Dgtest=false (dns block)
  • wrapdb.mesonbuild.com
    • Triggering command: /home/REDACTED/.local/bin/meson meson setup /tmp/lc0build --wipe -Dgtest=false (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

Implement the first two stages of refactoring classic search search-local state in borg323/lc0.

Repository: borg323/lc0
Base ref: backprop3

Context:

  • This branch already tracks full node paths during search and avoids depending on Node::parent_ for active search path handling.
  • We want to continue that refactor, but only for the first two steps discussed:
    1. Introduce a search-local tree/overlay (e.g. SearchTree) keyed to the persistent Node tree, to own search-local path / ancestry / collision bookkeeping as appropriate.
    2. Move n_in_flight_ / virtual-loss accounting out of Node and into that search-local tree / overlay.
  • The third step (removing Node::parent_ / fully migrating all ancestry reconstruction) should NOT be done in this PR.

Requirements:

  1. Create a pull request with exactly two commits:
    • Commit 1: introduce the search-local tree/overlay abstraction and migrate the relevant search-local bookkeeping to use it where practical, without yet removing Node::parent_.
    • Commit 2: move n_in_flight_ / virtual loss accounting out of Node into the search-local tree/overlay and update selection / collision / verbose stats / prefetch / backup related code accordingly.
  2. Keep Node as the persistent game-tree state holder for long-lived data such as edges, n_, wl_, d_, m_, bounds, terminal flags, etc.
  3. Do NOT remove Node::parent_, GetParent(), or GetOwnEdge() in this PR.
  4. Ensure all classic search code continues to work, especially these areas:
    • minibatch gathering
    • collision collection/cancelation
    • out-of-order evaluation handling
    • prefetch into cache
    • verbose stats / move stats output
    • sticky endgame backup / bounds propagation
  5. Update any helper APIs and types needed in src/search/classic/search.h, src/search/classic/search.cc, src/search/classic/node.h, and src/search/classic/node.cc, and add new files if appropriate.
  6. Remove n_in_flight_ and related APIs from Node only in the second commit, after the search-local replacement exists.
  7. Preserve existing behavior as closely as possible.
  8. Build/test as much as the agent can and summarize any validation performed in the PR body.

Suggested design direction:

  • SearchTree should be a lightweight per-search overlay keyed by Node*, rather than a duplicated ownership tree.
  • It should own search-local state such as:
    • per-node in-flight / started-visit accounting
    • path/ancestor lookup needed by the active search
    • shared collision bookkeeping where useful
  • Prefer keeping the persistent node tree structure in Node.
  • Since step 3 is deferred, history reconstruction that currently uses Node::parent_ may remain as-is unless there is a clear benefit to routing through the new overlay.

Please make the commit boundaries clean and explain them clearly in the PR description.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: This branch keeps track of the node path during the search and avoids use of the parent pointer in the Node. I would like to make those paths into some kind of a local tree (say SearchTree) and also move the n_in_flight_ counter out of the Node and into this tree as it logically belongs in the search.

Assistant: Yes — the refactor direction makes sense, and the current code is already halfway there.

The cleanest design is:

  1. keep Node as persistent game-tree state only,
  2. introduce a per-search SearchTree that owns:
    • parent/path relationships needed during this search,
    • n_in_flight / virtual-loss accounting,
    • collision bookkeeping,
  3. make selection / backup operate on SearchTree entries while still storing long-lived stats (n_, wl_, d_, m_, bounds, children/edges) in Node.

Why this is a good split

Right now Node mixes two concerns:

  • persistent tree state: n_, eval aggregates, bounds, edges, child storage
  • ephemeral search state: parent_ usage during search, n_in_flight_, collision path bookkeeping

Your branch already started separating path tracking from Node::parent_ by carrying full paths in NodeToProcess and task workspaces:

  • NodeToProcess::path in search.h
  • TaskWorkspace::full_path in search.h
  • PickTask::start_path in search.h

So introducing a SearchTree is a natural next step rather than a conceptual jump.

The strongest reason to move n_in_flight_ out of Node

n_in_flight_ is not really a property of the reused game tree; it's a property of the currently running search.

That shows up in several ways:

  • it is used only for active selection / collision handling
  • it must be canceled on teardown (CancelSharedCollisions)
  • it complicates node reuse semantics
  • it is one of the main reasons Node has search-time synchronization coupling
  • it blocks/special-cases transforms like MakeSolid() because “in flight” state is embedded in the persistent nodes

So movin...

This pull request was created from Copilot chat.

Copilot AI and others added 2 commits April 28, 2026 22:07
Add SearchTree as a lightweight per-search overlay keyed by Node*. The
overlay owns search-local bookkeeping that should not live in the
persistent Node tree.

In this first commit the overlay takes ownership of the shared collision
path bookkeeping (previously stored as Search::shared_collisions_). The
SearchTree class and its per-node SearchTreeNode struct are defined in
the new files src/search/classic/search_tree.{h,cc}.

Changes:
- New src/search/classic/search_tree.h: SearchTreeNode struct (n_in_flight
  placeholder) and SearchTree class with collision bookkeeping and
  n_in_flight API stubs for the next commit.
- New src/search/classic/search_tree.cc: implementation.
- meson.build: add search_tree.cc to the build.
- search.h: include search_tree.h, add search_tree_ member, remove
  shared_collisions_ (moved into SearchTree).
- search.cc: initialise search_tree_ in constructor; delegate
  CancelSharedCollisions() and CollectCollisions() to search_tree_.

Agent-Logs-Url: https://github.com/borg323/lc0/sessions/a12b09e2-9933-49f1-8fd7-127528dd6d83

Co-authored-by: borg323 <39573933+borg323@users.noreply.github.com>
Remove n_in_flight_ from the persistent Node and move all virtual-loss
/ in-flight accounting into SearchTree (introduced in the previous
commit).

Changes:
- node.h: remove n_in_flight_ field, GetNInFlight(), GetNStarted(),
  TryStartScoreUpdate(), CancelScoreUpdate(), IncrementNInFlight().
  Update EdgeAndNode::GetU() to take an explicit nstarted parameter
  (callers now compute it via search_tree_->GetNStarted()).
  Simplify VisitedNode_Iterator: now stops at the first N=0 node;
  the n_in_flight-based early-exit optimisation is replaced by a
  simpler N==0 check (equivalent for the common case).
  Update static_assert: sizeof(Node) 64 → 56.
- node.cc: remove TryStartScoreUpdate/CancelScoreUpdate
  implementations. Update FinalizeScoreUpdate to no longer decrement
  n_in_flight_. Update MakeSolid to remove n_in_flight safety checks
  (function is still disabled via #if 0 in the backup path).
- search_tree.h / search_tree.cc: add full n_in_flight API:
  TryStartScoreUpdate, CancelScoreUpdate, IncrementNInFlight,
  FinalizeScoreUpdate, GetNInFlight, GetNStarted.
- search.cc: update all call sites:
  * PickNodesToExtendTask: TryStartScoreUpdate, IncrementNInFlight,
    GetNStarted → search_tree_ equivalents.
  * GatherMinibatch OOO: CancelScoreUpdate, IncrementNInFlight →
    search_tree_.
  * DoBackupUpdateSingleNode: add search_tree_->FinalizeScoreUpdate.
  * GetVerboseStats: GetNInFlight/GetNStarted → search_tree_.
  * PrefetchIntoCache: GetNStarted → search_tree_->GetNStarted.

Build validated: ninja build passes cleanly on both commits.

Agent-Logs-Url: https://github.com/borg323/lc0/sessions/a12b09e2-9933-49f1-8fd7-127528dd6d83

Co-authored-by: borg323 <39573933+borg323@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor classic search by introducing search-local tree overlay classic search: introduce SearchTree overlay and move n_in_flight out of Node Apr 28, 2026
Copilot AI requested a review from borg323 April 28, 2026 22:20
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.

2 participants