-
Notifications
You must be signed in to change notification settings - Fork 599
One-query implementation of search by Codex. #2323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…; hook into build via meson
|
Quick fix for opponent move display and a few other issues I found: --- a/src/search/codex/codex.cc
+++ b/src/search/codex/codex.cc
@@ -104,6 +104,7 @@ class CodexSearch : public SearchBase {
params_ = params;
// Compute simple time budget.
ComputeTimeBudget();
+ if (worker_.joinable()) worker_.join();
worker_ = std::thread(&CodexSearch::SearchLoop, this);
}
@@ -270,6 +271,7 @@ class CodexSearch : public SearchBase {
// Prepare and send a thinking info update (best-so-far line and score).
void SendThinkingInfo() {
if (!root_ || root_->edges.empty()) return;
+ if (responded_bestmove_) return;
const int64_t elapsed_ms = move_start_time_ ?
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - *move_start_time_).count() : 0;
@@ -283,6 +285,11 @@ class CodexSearch : public SearchBase {
[](const Edge& a, const Edge& b) { return a.n < b.n; });
if (it == node->edges.end() || it->n == 0) break;
pv.push_back(it->move);
+ // Temporary compatibility: moves are encoded from current player
+ // perspective, flip before sending when black to move.
+ if (game_state_.CurrentPosition().IsBlackToMove() == (pv.size() & 1)) {
+ pv.back().Flip();
+ }
node = it->child.get();
if (pv.size() >= 16) break;
}
@@ -338,6 +345,7 @@ class CodexSearch : public SearchBase {
// perspective, flip before sending when black to move.
if (game_state_.CurrentPosition().IsBlackToMove()) {
if (!info.bestmove.is_null()) info.bestmove.Flip();
+ } else {
if (!info.ponder.is_null()) info.ponder.Flip();
}
uci_responder_->OutputBestMove(&info);
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a minimal PUCT (Predictor + Upper Confidence bounds applied to Trees) search implementation named "Codex" for the Leela Chess Zero engine. This implementation provides a simple, single-threaded alternative to the main search engine for demonstration and experimentation purposes.
Key Changes:
- Implements a lightweight PUCT tree search using direct Backend API calls with basic node selection, expansion, and backpropagation
- Registers the new search implementation as "codex" via the search factory pattern
- Integrates the implementation into the build system
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/search/codex/codex.cc | Complete implementation of CodexSearch class with PUCT algorithm, node/edge structures, and UCI integration |
| meson.build | Adds codex.cc to the build file list |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| void SetBackend(Backend* backend) override { | ||
| // Wrap to respect backend maximum batch sizes automatically. | ||
| batchsplit_backend_ = CreateBatchSplitingBackend(backend); |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'CreateBatchSplitingBackend' to 'CreateBatchSplittingBackend'.
| batchsplit_backend_ = CreateBatchSplitingBackend(backend); | |
| batchsplit_backend_ = CreateBatchSplittingBackend(backend); |
| int best_idx = -1; | ||
| float best_score = -std::numeric_limits<float>::infinity(); | ||
| for (size_t i = 0; i < node->edges.size(); ++i) { | ||
| const Edge& e = node->edges[i]; | ||
| const float q = (e.n > 0) ? (e.w / e.n) : 0.0f; | ||
| const float u = kCpuct * e.prior * sqrt_total / (1.0f + e.n); |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Division operation inside the selection loop could be optimized by precomputing 1.0f / (1.0f + e.n) or by computing the common factor kCpuct * sqrt_total once before the loop, reducing redundant calculations per child edge.
| int best_idx = -1; | |
| float best_score = -std::numeric_limits<float>::infinity(); | |
| for (size_t i = 0; i < node->edges.size(); ++i) { | |
| const Edge& e = node->edges[i]; | |
| const float q = (e.n > 0) ? (e.w / e.n) : 0.0f; | |
| const float u = kCpuct * e.prior * sqrt_total / (1.0f + e.n); | |
| const float cpuct_sqrt = kCpuct * sqrt_total; | |
| int best_idx = -1; | |
| float best_score = -std::numeric_limits<float>::infinity(); | |
| for (size_t i = 0; i < node->edges.size(); ++i) { | |
| const Edge& e = node->edges[i]; | |
| const float q = (e.n > 0) ? (e.w / e.n) : 0.0f; | |
| const float recip = 1.0f / (1.0f + e.n); | |
| const float u = cpuct_sqrt * e.prior * recip; |
|
|
||
| // Counters/metrics | ||
| int max_depth_ = 0; | ||
| int64_t nodes_visited_ = 0; |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nodes_visited_ counter is accessed from both the worker thread (incrementing in RunOneSimulation) and potentially the main thread during shutdown/info updates, but is not protected by atomics or mutex. This creates a data race. Consider making it std::atomic<int64_t> or protecting access with the appropriate synchronization mechanism.
| int64_t nodes_visited_ = 0; | |
| std::atomic<int64_t> nodes_visited_{0}; |
| std::optional<std::chrono::steady_clock::time_point> move_start_time_; | ||
|
|
||
| // Counters/metrics | ||
| int max_depth_ = 0; |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The max_depth_ variable is updated in RunOneSimulation (line 251) on the worker thread and read in SendThinkingInfo without synchronization. This is a data race. Consider using std::atomic<int> for thread-safe access.
| int max_depth_ = 0; | |
| std::atomic<int> max_depth_{0}; |
search(codex): add minimal PUCT search implementation and register it; hook into build via meson