Skip to content
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

Certainty propagation negabound [WIP] #487

Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
54a199a
Efficient and informative depth computation.
Videodr0me Aug 12, 2018
b6c88dc
Update comments and replace tabs with spaces
Videodr0me Aug 13, 2018
db3b419
Merge remote-tracking branch 'upstream/master'
Videodr0me Oct 17, 2018
fbebb38
Merge remote-tracking branch 'upstream/master'
Videodr0me Oct 17, 2018
891ef72
Certainty Propagation
Videodr0me Nov 3, 2018
5f8aff0
Fixes compiler warnings/errors on -pedantic.
Videodr0me Nov 3, 2018
c664fb5
Resolve merge conflicts
Videodr0me Nov 4, 2018
560e413
Resolve Merge Conflicts 2
Videodr0me Nov 4, 2018
4078af0
Merge: unexpected behaviour when go infinite fixed
Videodr0me Nov 4, 2018
4fb5522
Speed fix. Reading non-cached parameters was slow. Now using cached v…
Videodr0me Nov 4, 2018
84113e5
Speed fix. Used reserve in pseudo legal move generation. If compiled …
Videodr0me Nov 5, 2018
aa266eb
Fix for CP=2, CP=2 (default for play) is now more conservative and ad…
Videodr0me Nov 6, 2018
f087312
Bugfixes, codecleanup minor changes:
Videodr0me Dec 1, 2018
fdbe61a
Rename ClearEdge
Videodr0me Dec 1, 2018
ffee98f
change build-cuda to latest
Videodr0me Dec 2, 2018
4920e74
use optional info.mate to display mate scores
Videodr0me Dec 3, 2018
5e726b4
display 0.00 for tablebase draws when syzygy filtered
Videodr0me Dec 4, 2018
aba68a7
Finalize this WIP PR:
Videodr0me Dec 9, 2018
fec72f0
merge with master
Videodr0me Dec 9, 2018
1b7ac55
merge with master
Videodr0me Jan 18, 2019
9f21d9d
Prefer terminal wins over certain wins, to avoid delaying mate. Pref…
Videodr0me Jan 26, 2019
27bdf3a
update to master
Videodr0me Jan 26, 2019
c206f67
code now in sync with basic certainty propagation
Videodr0me Jan 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ subprojects/*
!subprojects/*.wrap
lc0.xcodeproj/
*.swp
.vs/
build-cuda.cmd
16 changes: 11 additions & 5 deletions build-cuda.cmd
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
rd /s build

call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
meson.py build --backend vs2015 --buildtype release ^
-Dcudnn_libdirs="C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.2\lib\x64","C:\dev\cuDNN\cuda\lib\x64" ^
-Dcudnn_include="C:\dev\cuDNN\cuda\include" ^
rem set MSBuild="C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe"
set MSBuild="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"
rem call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
meson.py build --backend vs2017 --buildtype release ^
-Dcudnn_libdirs="C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0\lib\x64","C:\dev\cuDNN\cuda\lib\x64" ^
-Dcudnn_include="C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0\include","C:\dev\cuDNN\cuda\include" ^
-Ddefault_library=static

pause


cd build

"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" ^
%MSBuild% ^
/p:Configuration=Release ^
/p:Platform=x64 ^
/p:PreferredToolArchitecture=x64 lc0.sln ^
Expand Down
1 change: 1 addition & 0 deletions src/chess/board.cc
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ BitBoard ChessBoard::en_passant() const { return pawns_ - pawns(); }

MoveList ChessBoard::GeneratePseudolegalMoves() const {
MoveList result;
result.reserve(60);
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment why it is needed and why 60 is picked.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It slighty speeded up move generation in profiling. Number is a guestimate - 20 was to little, and 100 seemingly to much. This is not based on rigerous testing.

for (auto source : our_pieces_) {
// King
if (source == our_king_) {
Expand Down
2 changes: 2 additions & 0 deletions src/chess/callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ struct ThinkingInfo {
int hashfull = -1;
// Win in centipawns.
optional<int> score;
// Distance to Mate
optional<int> mate;
// Number of successful TB probes (not the same as playouts ending in TB hit).
int tb_hits = -1;
// Best line found. Moves are from perspective of white player.
Expand Down
1 change: 1 addition & 0 deletions src/chess/position.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Position {
};

enum class GameResult { UNDECIDED, WHITE_WON, DRAW, BLACK_WON };
enum class CertaintyTrigger { NONE, TB_HIT, TWO_FOLD, TERMINAL, NORMAL };

class PositionHistory {
public:
Expand Down
8 changes: 7 additions & 1 deletion src/chess/uciloop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,13 @@ void UciLoop::SendInfo(const std::vector<ThinkingInfo>& infos) {
if (info.seldepth >= 0) res += " seldepth " + std::to_string(info.seldepth);
if (info.time >= 0) res += " time " + std::to_string(info.time);
if (info.nodes >= 0) res += " nodes " + std::to_string(info.nodes);
if (info.score) res += " score cp " + std::to_string(*info.score);

// If mate display mate, otherwise if score display score.
if (info.mate) {
res += " score mate " + std::to_string(*info.mate);
} else if (info.score) {
res += " score cp " + std::to_string(*info.score);
}
if (info.hashfull >= 0) res += " hashfull " + std::to_string(info.hashfull);
if (info.nps >= 0) res += " nps " + std::to_string(info.nps);
if (info.tb_hits >= 0) res += " tbhits " + std::to_string(info.tb_hits);
Expand Down
1 change: 1 addition & 0 deletions src/chess/uciloop.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class UciLoop {
virtual void CmdStop() { throw Exception("Not supported"); }
virtual void CmdPonderHit() { throw Exception("Not supported"); }
virtual void CmdStart() { throw Exception("Not supported"); }
virtual void CmdStats() { throw Exception("Not supported"); }

private:
bool DispatchCommand(
Expand Down
8 changes: 6 additions & 2 deletions src/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

namespace lczero {
namespace {
const int kDefaultThreads = 2;
const int kDefaultThreads = 4;
Copy link
Member

Choose a reason for hiding this comment

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

I think we should not change default number of threads without testing as a part of larger change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe you have a point. If I revert this back to 2 threads, i would like to include a warning though that CP requires more threads especially on fast GPU systems. Maybe in the option descriptions? But thats not to nice either. Ideas?


const OptionId kThreadsOptionId{"threads", "Threads",
"Number of (CPU) worker threads to use.", 't'};
Expand Down Expand Up @@ -98,7 +98,6 @@ const size_t kAvgNodeSize = sizeof(Node) + kAvgMovesPerPosition * sizeof(Edge);
const size_t kAvgCacheItemSize =
NNCache::GetItemStructSize() + sizeof(CachedNNRequest) +
sizeof(CachedNNRequest::IdxAndProb) * kAvgMovesPerPosition;

float ComputeMoveWeight(int ply, float peak, float left_width,
float right_width) {
// Inflection points of the function are at ply = peak +/- width.
Expand Down Expand Up @@ -154,6 +153,8 @@ void EngineController::PopulateOptions(OptionsParser* options) {
defaults->Set<int>(SearchParams::kMaxCollisionEventsId.GetId(), 32);
defaults->Set<int>(SearchParams::kCacheHistoryLengthId.GetId(), 0);
defaults->Set<bool>(SearchParams::kOutOfOrderEvalId.GetId(), true);
defaults->Set<int>(SearchParams::kCertaintyPropagationId.GetId(), 2);
defaults->Set<int>(SearchParams::kCertaintyPropagationDepthId.GetId(), 1);
}

SearchLimits EngineController::PopulateSearchLimits(
Expand Down Expand Up @@ -356,6 +357,7 @@ void EngineController::Go(const GoParams& params) {
if (info.multipv <= 1) {
ponder_info = info;
if (ponder_info.score) ponder_info.score = -*ponder_info.score;
if (ponder_info.mate) ponder_info.mate = -*ponder_info.mate;
if (ponder_info.depth > 1) ponder_info.depth--;
if (ponder_info.seldepth > 1) ponder_info.seldepth--;
ponder_info.pv.clear();
Expand Down Expand Up @@ -465,4 +467,6 @@ void EngineLoop::CmdPonderHit() { engine_.PonderHit(); }

void EngineLoop::CmdStop() { engine_.Stop(); }



} // namespace lczero
3 changes: 2 additions & 1 deletion src/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ class EngineController {
void PonderHit();
// Must not block.
void Stop();

// Prints verbose move stats of current root
Copy link
Member

Choose a reason for hiding this comment

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

Period in the end of the sentence.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Deleted the stats command.


SearchLimits PopulateSearchLimits(int ply, bool is_black,
const GoParams& params,
std::chrono::steady_clock::time_point start_time);
Expand Down
108 changes: 90 additions & 18 deletions src/mcts/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
#include "mcts/node.h"

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <thread>
Expand Down Expand Up @@ -160,9 +162,58 @@ float Edge::GetP() const {
return ret;
}

void Edge::MakeTerminal(GameResult result) {
certainty_state_ |= kTerminalMask | kCertainMask | kUpperBound | kLowerBound;
certainty_state_ &= kGameResultClear;
if (result == GameResult::WHITE_WON) {
certainty_state_ |= kGameResultWin;
} else if (result == GameResult::BLACK_WON) {
certainty_state_ |= kGameResultLoss;
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Run clang-format, it will fix long lines and double empty lines between functions.

void Edge::MakeCertain(GameResult result, CertaintyTrigger trigger) {
certainty_state_ |= kCertainMask | kUpperBound | kLowerBound;
certainty_state_ &= kGameResultClear;
if (result == GameResult::WHITE_WON) {
certainty_state_ |= kGameResultWin;
} else if (result == GameResult::BLACK_WON) {
certainty_state_ |= kGameResultLoss;
}
if (trigger == CertaintyTrigger::TB_HIT) certainty_state_ |= kTBHit;
if (trigger == CertaintyTrigger::TWO_FOLD) certainty_state_ |= kTwoFold;
}

void Edge::MakeCertain(int q, CertaintyTrigger trigger) {
certainty_state_ |= kCertainMask | kUpperBound | kLowerBound;
certainty_state_ &= kGameResultClear;
if (q == 1) {
certainty_state_ |= kGameResultWin;
} else if (q == -1) {
certainty_state_ |= kGameResultLoss;
}
if (trigger == CertaintyTrigger::TB_HIT) certainty_state_ |= kTBHit;
if (trigger == CertaintyTrigger::TWO_FOLD) certainty_state_ |= kTwoFold;
}
void Edge::SetEQ(int eq) {
certainty_state_ &= kGameResultClear;
if (eq == 1) {
certainty_state_ |= kGameResultWin;
} else if (eq == -1) {
certainty_state_ |= kGameResultLoss;
}
}

int Edge::GetEQ() const {
if (certainty_state_ & kGameResultLoss) return -1;
if (certainty_state_ & kGameResultWin) return 1;
return 0;
}

std::string Edge::DebugString() const {
std::ostringstream oss;
oss << "Move: " << move_.as_string() << " p_: " << p_ << " GetP: " << GetP();
oss << "Move: " << move_.as_string() << " p_: " << p_ << " GetP: " << GetP()
<< " Certainty:" << std::bitset<8>(certainty_state_);
return oss.str();
}

Expand Down Expand Up @@ -197,36 +248,50 @@ void Node::CreateEdges(const MoveList& moves) {
Node::ConstIterator Node::Edges() const { return {edges_, &child_}; }
Node::Iterator Node::Edges() { return {edges_, &child_}; }

// Recalculate n_ from real children visits.
// This is needed if a node was proved to be certain in a prior
// search and later gets to be root of search.
void Node::RecomputeNfromChildren() {
if (n_ > 1) {
uint32_t visits = 1;
for (const auto& child : Edges()) visits += child.GetN();
n_ = visits;
}
assert(n_in_flight_ == 0);
}

float Node::GetVisitedPolicy() const { return visited_policy_; }

float Node::GetQ() const {
if (parent_) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't really like relying much on a parent, as it was a temporary optimization only.
Is Q conceptually edge's or node's property?
If edge's: Why do we need to check "if edge is certain" in a node rather than edge?
If node's: Why do we store that it's certain in an edge rather than a node?

Node should not go to parent to get some information about itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I implemented it as to be as minimal invasive to the current codebase. You are correct that overall it would be "nicer" to move from a node centric style to a edge.node or even edge centric style. I left the parts of the code as is, if they checked for terminal/certain status by node. I would propose changing this only after CP is tested and with the complete codebase and future changes in mind.

So while I feel that there is currently a shift towards a more edge centric view with your new detachment scheme which might store N in the edge as well, and also even the original A0 seems rather edge centric), I would still make that a seperate PR sometime later.

auto edge = parent_->GetEdgeToNode(this);
if (edge->IsCertain()) return (float)edge->GetEQ();
}
return q_;
}

Edge* Node::GetEdgeToNode(const Node* node) const {
assert(node->parent_ == this);
assert(node->index_ < edges_.size());
return &edges_[node->index_];
}

Edge* Node::GetOwnEdge() const { return GetParent()->GetEdgeToNode(this); }
Edge* Node::GetOwnEdge() const {
if (GetParent()) {
return GetParent()->GetEdgeToNode(this);
} else
return nullptr;
}

std::string Node::DebugString() const {
std::ostringstream oss;
oss << " Term:" << is_terminal_ << " This:" << this << " Parent:" << parent_
<< " Index:" << index_ << " Child:" << child_.get()
<< " Sibling:" << sibling_.get() << " Q:" << q_ << " N:" << n_
<< " N_:" << n_in_flight_ << " Edges:" << edges_.size();
oss << " This:" << this << " Parent:" << parent_ << " Index:" << index_
<< " Child:" << child_.get() << " Sibling:" << sibling_.get()
<< " Q:" << q_ << " N:" << n_ << " N_:" << n_in_flight_
<< " Edges:" << edges_.size();
return oss.str();
}

void Node::MakeTerminal(GameResult result) {
is_terminal_ = true;
if (result == GameResult::DRAW) {
q_ = 0.0f;
} else if (result == GameResult::WHITE_WON) {
q_ = 1.0f;
} else if (result == GameResult::BLACK_WON) {
q_ = -1.0f;
}
}

bool Node::TryStartScoreUpdate() {
if (n_ == 0 && n_in_flight_ > 0) return false;
++n_in_flight_;
Expand Down Expand Up @@ -358,6 +423,8 @@ void NodeTree::MakeMove(Move move) {
current_head_->ReleaseChildrenExceptOne(new_head);
current_head_ =
new_head ? new_head : current_head_->CreateSingleChildNode(move);
if (current_head_->GetParent()) current_head_->GetOwnEdge()->ClearCertaintyState();
current_head_->RecomputeNfromChildren();
history_.Append(move);
}

Expand Down Expand Up @@ -402,8 +469,13 @@ bool NodeTree::ResetToPosition(const std::string& starting_fen,
// previously trimmed; we need to reset current_head_ in that case.
// Also, if the current_head_ is terminal, reset that as well to allow forced
// analysis of WDL hits, or possibly 3 fold or 50 move "draws", etc.
if (!seen_old_head || current_head_->IsTerminal()) TrimTreeAtHead();
if (!seen_old_head) TrimTreeAtHead(); // must check if edges are created

// Certainty Propagation: No need to trim the head, just resetting certainty
// state and recomputing N should suffice even with WDL hits.
// TODO: Works, but maybe recheck if there are crititcal edge cases.
if (current_head_->GetParent()) current_head_->GetOwnEdge()->ClearCertaintyState();
current_head_->RecomputeNfromChildren();
return seen_old_head;
}

Expand Down
Loading