-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathselfplay.cpp
More file actions
222 lines (187 loc) · 7.9 KB
/
Copy pathselfplay.cpp
File metadata and controls
222 lines (187 loc) · 7.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#include "core/global.h"
#include "core/makedir.h"
#include "core/config_parser.h"
#include "core/timer.h"
#include "dataio/sgf.h"
#include "search/asyncbot.h"
#include "program/setup.h"
#include "program/play.h"
#include "program/gitinfo.h"
#include "main.h"
using namespace std;
#define TCLAP_NAMESTARTSTRING "-" //Use single dashes for all flags
#include <tclap/CmdLine.h>
#include <csignal>
static std::atomic<bool> sigReceived(false);
static void signalHandler(int signal)
{
if(signal == SIGINT || signal == SIGTERM)
sigReceived.store(true);
}
int MainCmds::selfPlay(int argc, const char* const* argv) {
Board::initHash();
Rand seedRand;
string configFile;
string logFile;
string modelFile;
string sgfOutputDir;
string trainDataOutputDir;
try {
TCLAP::CmdLine cmd("Generate training data via self play", ' ', "1.0",true);
TCLAP::ValueArg<string> configFileArg("","config-file","Config file to use",true,string(),"FILE");
TCLAP::ValueArg<string> logFileArg("","log-file","Log file to output to",true,string(),"FILE");
//TODO do this instead
//TCLAP::ValueArg<string> modelsDirArg("","models-dir","Dir to poll and load models from",true,string(),"DIR");
TCLAP::ValueArg<string> modelFileArg("","model-file","Neural net model file to use",true,string(),"FILE");
TCLAP::ValueArg<string> sgfOutputDirArg("","sgf-output-dir","Dir to output sgf files",true,string(),"DIR");
TCLAP::ValueArg<string> trainDataOutputDirArg("","train-data-output-dir","Dir to output training data",true,string(),"DIR");
cmd.add(configFileArg);
cmd.add(logFileArg);
//cmd.add(modelsDirArg);
cmd.add(modelFileArg);
cmd.add(sgfOutputDirArg);
cmd.add(trainDataOutputDirArg);
cmd.parse(argc,argv);
configFile = configFileArg.getValue();
logFile = logFileArg.getValue();
modelFile = modelFileArg.getValue();
sgfOutputDir = sgfOutputDirArg.getValue();
trainDataOutputDir = trainDataOutputDirArg.getValue();
}
catch (TCLAP::ArgException &e) {
cerr << "Error: " << e.error() << " for argument " << e.argId() << endl;
return 1;
}
ConfigParser cfg(configFile);
Logger logger;
logger.addFile(logFile);
bool logToStdout = cfg.getBool("logToStdout");
logger.setLogToStdout(logToStdout);
bool logSearchInfo = cfg.getBool("logSearchInfo");
bool logMoves = cfg.getBool("logMoves");
int64_t logGamesEvery = cfg.getInt64("logGamesEvery",1,1000000);
logger.write("Self Play Engine starting...");
logger.write(string("Git revision: ") + GIT_REVISION);
//TODO we should dynamically randomize the no result and draw utilities, and provide them as inputs to the net?
SearchParams params;
{
vector<SearchParams> paramss = Setup::loadParams(cfg);
if(paramss.size() != 1)
throw StringError("Can only specify one set of search parameters for self-play");
params = paramss[0];
}
NNEvaluator* nnEval;
{
Setup::initializeSession(cfg);
vector<NNEvaluator*> nnEvals = Setup::initializeNNEvaluators({modelFile},cfg,logger,seedRand);
assert(nnEvals.size() == 1);
nnEval = nnEvals[0];
}
logger.write("Loaded neural net");
//Initialize object for randomizing game settings
GameInitializer* gameInit = new GameInitializer(cfg);
//Load runner settings
int numMatchThreads = cfg.getInt("numGameThreads",1,16384);
int maxMovesPerGame = cfg.getInt("maxMovesPerGame",1,1 << 30);
string searchRandSeedBase = Global::uint64ToHexString(seedRand.nextUInt64());
//Check for unused config keys
{
vector<string> unusedKeys = cfg.unusedKeys();
for(size_t i = 0; i<unusedKeys.size(); i++) {
string msg = "WARNING: Unused key '" + unusedKeys[i] + "' in " + configFile;
logger.write(msg);
cerr << msg << endl;
}
}
//Done loading!
//------------------------------------------------------------------------------------
logger.write("Loaded all config stuff, starting self play");
if(!logToStdout)
cout << "Loaded all config stuff, starting self play" << endl;
//TODO write to subdirs once we have proper polling for new nn models
if(sgfOutputDir != string())
MakeDir::make(sgfOutputDir);
if(!std::atomic_is_lock_free(&sigReceived))
throw StringError("sigReceived is not lock free, signal-quitting mechanism for terminating matches will NOT work!");
std::signal(SIGINT, signalHandler);
std::signal(SIGTERM, signalHandler);
auto runGame = [¶ms,&nnEval,&logger,logSearchInfo,logMoves,maxMovesPerGame,&searchRandSeedBase](
int64_t gameIdx, Board& board, Player pla, BoardHistory& hist, int numExtraBlack
) {
string searchRandSeed = searchRandSeedBase + ":" + Global::int64ToString(gameIdx);
Rand gameRand(searchRandSeed + ":" + "forGameRand");
//Avoid interactions between the two bots and make sure root noise is effective on each new search
bool clearBotAfterSearchThisGame = true;
//In 2% of games, don't autoterminate the game upon all pass alive, to just provide a tiny bit of training data on positions that occur
//as both players must wrap things up manually, because within the search we don't autoterminate games, meaning that the NN will get
//called on positions that occur after the game would have been autoterminated.
bool doEndGameIfAllPassAlive = gameRand.nextBool(0.98);
AsyncBot* bot = new AsyncBot(params, nnEval, &logger, searchRandSeed);
Play::runGame(
board,pla,hist,numExtraBlack,bot,bot,
doEndGameIfAllPassAlive,clearBotAfterSearchThisGame,
logger,logSearchInfo,logMoves,
maxMovesPerGame,sigReceived
);
delete bot;
};
mutex gameSetupMutex;
int64_t numGamesStartedSoFar = 0;
auto runMatchLoop = [
&gameInit,&runGame,&gameSetupMutex,
&numGamesStartedSoFar,&sgfOutputDir,&logger,logGamesEvery,
&modelFile,&nnEval
](
uint64_t threadHash
) {
unique_lock<std::mutex> lock(gameSetupMutex,std::defer_lock);
//TODO once we have polling this needs to go to a subdir, one for each new net
ofstream* sgfOut = sgfOutputDir.length() > 0 ? (new ofstream(sgfOutputDir + "/" + Global::uint64ToHexString(threadHash) + ".sgfs")) : NULL;
while(true) {
if(sigReceived.load())
break;
lock.lock();
int64_t gameIdx = numGamesStartedSoFar;
numGamesStartedSoFar += 1;
if(numGamesStartedSoFar % logGamesEvery == 0)
logger.write("Started " + Global::int64ToString(numGamesStartedSoFar) + " games");
int logNNEvery = logGamesEvery > 1000 ? logGamesEvery : 1000;
//TODO this also needs to adapt a bit and make sure to be threadsafe when we dynamcally change the nneval
if(numGamesStartedSoFar % logNNEvery == 0) {
logger.write(modelFile);
logger.write("NN rows: " + Global::int64ToString(nnEval->numRowsProcessed()));
logger.write("NN batches: " + Global::int64ToString(nnEval->numBatchesProcessed()));
logger.write("NN avg batch size: " + Global::doubleToString(nnEval->averageProcessedBatchSize()));
}
lock.unlock();
Board board; Player pla; BoardHistory hist; int numExtraBlack;
gameInit->createGame(board,pla,hist,numExtraBlack);
Board initialBoard = board;
Rules initialRules = hist.rules;
runGame(gameIdx,board,pla,hist,numExtraBlack);
if(sigReceived.load())
break;
if(sgfOut != NULL) {
//TODO this needs to adapt once we have polling for the net
string playerName = "bot";
WriteSgf::writeSgf(*sgfOut,playerName,playerName,initialRules,initialBoard,hist);
(*sgfOut) << endl;
}
}
if(sgfOut != NULL)
sgfOut->close();
};
Rand hashRand;
vector<std::thread> threads;
for(int i = 0; i<numMatchThreads; i++) {
threads.push_back(std::thread(runMatchLoop, hashRand.nextUInt64()));
}
for(int i = 0; i<numMatchThreads; i++)
threads[i].join();
delete nnEval;
NeuralNet::globalCleanup();
if(sigReceived.load())
logger.write("Exited cleanly after signal");
logger.write("All cleaned up, quitting");
return 0;
}