diff --git a/worker/games.py b/worker/games.py index 64463a3c5..f65e1cd6f 100644 --- a/worker/games.py +++ b/worker/games.py @@ -67,7 +67,7 @@ def is_64bit(): HTTP_TIMEOUT = 30.0 -CUTECHESS_KILL_TIMEOUT = 15.0 +FASTCHESS_KILL_TIMEOUT = 15.0 UPDATE_RETRY_TIME = 15.0 RAWCONTENT_HOST = "https://raw.githubusercontent.com" @@ -918,6 +918,7 @@ def result_to_score(_result): odd = round_ even = round_ + 1 if odd in rounds.keys() and even in rounds.keys(): + print("Hi there:", rounds) assert rounds[odd]["white"][0:3] == "New" assert rounds[odd]["white"] == rounds[even]["black"] assert rounds[odd]["black"] == rounds[even]["white"] @@ -955,10 +956,11 @@ def results_to_score(results): s5 = results_to_score(rounds["pentanomial"]) + results_to_score(rounds["trinomial"]) assert sum(LDW) == 2 * sum(rounds["pentanomial"]) + sum(rounds["trinomial"]) epsilon = 1e-4 + print("Here we have: ", wld, rounds, s3, s5) assert abs(s5 - s3) < epsilon -def parse_cutechess_output( +def parse_fastchess_output( p, current_state, remote, result, spsa_tuning, games_to_play, batch_size, tc_limit ): hash_pattern = re.compile(r"(Base|New)-[a-f0-9]+") @@ -1032,7 +1034,7 @@ def shorten_hash(match): validate_pentanomial( wld, rounds - ) # check if cutechess-cli result is compatible with + ) # check if fast-chess result is compatible with # our own bookkeeping pentanomial = [ @@ -1123,7 +1125,7 @@ def shorten_hash(match): return True -def launch_cutechess( +def launch_fastchess( cmd, current_state, remote, result, spsa_tuning, games_to_play, batch_size, tc_limit ): if spsa_tuning: @@ -1154,7 +1156,7 @@ def launch_cutechess( w_params = [] b_params = [] - # Run cutechess-cli binary. + # Run fast-chess binary. # Stochastic rounding and probability for float N.p: (N, 1-p); (N+1, p) idx = cmd.index("_spsa_") cmd = ( @@ -1179,7 +1181,7 @@ def launch_cutechess( + cmd[idx + 1 :] ) - # print(cmd) + print(cmd) try: with subprocess.Popen( cmd, @@ -1201,7 +1203,7 @@ def launch_cutechess( close_fds=not IS_WINDOWS, ) as p: try: - task_alive = parse_cutechess_output( + task_alive = parse_fastchess_output( p, current_state, remote, @@ -1212,15 +1214,15 @@ def launch_cutechess( tc_limit, ) finally: - # We nicely ask cutechess-cli to stop. + # We nicely ask fast-chess to stop. try: send_sigint(p) except Exception as e: print("\nException in send_sigint:\n", e, sep="", file=sys.stderr) # now wait... - print("\nWaiting for cutechess-cli to finish ... ", end="", flush=True) + print("\nWaiting for fast-chess to finish ... ", end="", flush=True) try: - p.wait(timeout=CUTECHESS_KILL_TIMEOUT) + p.wait(timeout=FASTCHESS_KILL_TIMEOUT) except subprocess.TimeoutExpired: print("timeout", flush=True) kill_process(p) @@ -1228,12 +1230,12 @@ def launch_cutechess( print("done", flush=True) except (OSError, subprocess.SubprocessError) as e: print( - "Exception starting cutechess:\n", + "Exception starting fast-chess:\n", e, sep="", file=sys.stderr, ) - raise WorkerException("Unable to start cutechess. Error: {}".format(str(e))) + raise WorkerException("Unable to start fast-chess. Error: {}".format(str(e))) return task_alive @@ -1249,7 +1251,7 @@ def run_games( clear_binaries, global_cache, ): - # This is the main cutechess-cli driver. + # This is the main fast-chess driver. # It is ok, and even expected, for this function to # raise exceptions, implicitly or explicitly, if a # task cannot be completed. @@ -1317,7 +1319,7 @@ def run_games( start_game_index = opening_offset + input_total_games run_seed = int(hashlib.sha1(run["_id"].encode("utf-8")).hexdigest(), 16) % (2**30) - # Format options according to cutechess syntax. + # Format options according to fastchess syntax. def parse_options(s): results = [] chunks = s.split("=") @@ -1405,7 +1407,7 @@ def parse_options(s): unzip(blob, testing_dir) # convert .epd containing FENs into .epd containing EPDs with move counters - # only needed as long as cutechess-cli is the game manager + # only needed as long as cutechess-cli is the game manager TODO TODO TODO TODO ? is this still needed ? if book.endswith(".epd"): convert_book_move_counters(testing_dir / book) @@ -1424,7 +1426,7 @@ def parse_options(s): file=sys.stderr, ) - # Add EvalFile* with full path to cutechess options, and download the networks if missing. + # Add EvalFile* with full path to fast-chess options, and download the networks if missing. for option, net in required_nets(base_engine).items(): base_options.append("option.{}={}".format(option, net)) establish_validated_net(remote, testing_dir, net, global_cache) @@ -1554,11 +1556,11 @@ def make_player(arg): if any(substring in book.upper() for substring in ["FRC", "960"]): variant = "fischerandom" - # Run cutechess binary. - cutechess = "cutechess-cli" + EXE_SUFFIX + # Run fastchess binary. + fastchess = "fast-chess" + EXE_SUFFIX cmd = ( [ - os.path.join(testing_dir, cutechess), + os.path.join(testing_dir, fastchess), "-recover", "-repeat", "-games", @@ -1618,7 +1620,7 @@ def make_player(arg): + book_cmd ) - task_alive = launch_cutechess( + task_alive = launch_fastchess( cmd, current_state, remote, diff --git a/worker/sri.txt b/worker/sri.txt index ee2cad5b6..5c8276633 100644 --- a/worker/sri.txt +++ b/worker/sri.txt @@ -1 +1 @@ -{"__version": 241, "updater.py": "Mg+pWOgGA0gSo2TuXuuLCWLzwGwH91rsW1W3ixg3jYauHQpRMtNdGnCfuD1GqOhV", "worker.py": "BMuQUpxZAKF0aP6ByTZY1r06MfPoIbdG2xraTrDQQRKgvhzJo6CKmeX2P8vX/QDm", "games.py": "9dFaa914vpqT7q4LLx2LlDdYwK6QFVX3h7+XRt18ATX0lt737rvFeBIiqakkttNC"} +{"__version": 241, "updater.py": "Mg+pWOgGA0gSo2TuXuuLCWLzwGwH91rsW1W3ixg3jYauHQpRMtNdGnCfuD1GqOhV", "worker.py": "mN6XqF6nS4B963RXxss41kw8bxunK6fLt1WJ5fQnlNkLUyjsVsYvuKgYVYDJcNRx", "games.py": "I9/zSBcifvMrKrSySiUe9jvV+kVRbWB8flV6hFvNL9aNty4aCwB/kLPXWhGKEprI"} diff --git a/worker/worker.py b/worker/worker.py index bfc40c95d..206f213cf 100644 --- a/worker/worker.py +++ b/worker/worker.py @@ -16,6 +16,7 @@ import stat import subprocess import sys +import tempfile import threading import time import traceback @@ -44,6 +45,7 @@ download_from_github, format_return_code, log, + requests_get, run_games, send_api_post_request, str_signal, @@ -103,8 +105,8 @@ worker.py : worker() worker.py : fetch_and_handle_task() [in loop] games.py : run_games() -games.py : launch_cutechess() [in loop for spsa] -games.py : parse_cutechess_output() +games.py : launch_fastchess() [in loop for spsa] +games.py : parse_fastchess_output() Apis used by the worker ======================= @@ -120,8 +122,8 @@ /api/request_task POST /api/nn/ GET /git/trees/master GET - /git/trees/master/blobs/ GET /git/trees/master/blobs/ GET + /repos/Disservin/fast-chess/zipball/ GET /repos//zipball/ GET Main loop /api/update_task POST @@ -392,40 +394,17 @@ def get_credentials(config, options, args): return username, password -def download_cutechess(cutechess, save_dir): - if len(EXE_SUFFIX) > 0: - zipball = "cutechess-cli-win.zip" - elif IS_MACOS: - zipball = "cutechess-cli-macos-64bit.zip" - else: - zipball = "cutechess-cli-linux-{}.zip".format(platform.architecture()[0]) - try: - blob = download_from_github(zipball) - unzip(blob, save_dir) - - os.chmod(cutechess, os.stat(cutechess).st_mode | stat.S_IEXEC) - except Exception as e: - print( - "Exception downloading or extracting {}:\n".format(zipball), - e, - sep="", - file=sys.stderr, - ) - else: - print("Finished downloading {}".format(cutechess)) +def verify_required_fastchess(fastchess_path): + # Verify that fastchess is working and has the required minimum version. - -def verify_required_cutechess(cutechess_path): - # Verify that cutechess is working and has the required minimum version. - - if not cutechess_path.exists(): + if not fastchess_path.exists(): return False - print("Obtaining version info for {} ...".format(cutechess_path)) + print("Obtaining version info for {} ...".format(fastchess_path)) try: with subprocess.Popen( - [cutechess_path, "--version"], + [fastchess_path, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, @@ -433,7 +412,7 @@ def verify_required_cutechess(cutechess_path): close_fds=not IS_WINDOWS, ) as p: errors = p.stderr.read() - pattern = re.compile(r"cutechess-cli ([0-9]+)\.([0-9]+)\.([0-9]+)") + pattern = re.compile("fast-chess alpha ([0-9]*).([0-9]*).([0-9]*)") major, minor, patch = 0, 0, 0 for line in iter(p.stdout.readline, ""): m = pattern.search(line) @@ -443,88 +422,80 @@ def verify_required_cutechess(cutechess_path): minor = int(m.group(2)) patch = int(m.group(3)) except (OSError, subprocess.SubprocessError) as e: - print("Unable to run cutechess-cli. Error: {}".format(str(e))) + print("Unable to run fast-chess. Error: {}".format(str(e))) return False if p.returncode != 0: print( - "Unable to run cutechess-cli. Return code: {}. Error: {}".format( + "Unable to run fast-chess. Return code: {}. Error: {}".format( format_return_code(p.returncode), errors ) ) return False if major + minor + patch == 0: - print("Unable to find the version of cutechess-cli.") + print("Unable to find the version of fast-chess.") return False - if (major, minor) < (1, 2): - print("Requires cutechess 1.2 or higher, found version doesn't match") + if (major, minor) < (0, 9): + print("Requires fast-chess 0.9 or higher, found version doesn't match") return False return True -def setup_cutechess(worker_dir): +def setup_fastchess(worker_dir): # Create the testing directory if missing. testing_dir = worker_dir / "testing" testing_dir.mkdir(exist_ok=True) - curr_dir = Path.cwd() + fastchess = "fast-chess" + EXE_SUFFIX + if verify_required_fastchess(testing_dir / fastchess): + return True + # build it ourselves try: - os.chdir(testing_dir) - except Exception as e: - print("Unable to enter {}. Error: {}".format(testing_dir, str(e))) - return False + fastchess_sha = "67dc441c55e33e30273b492f23e1ae0218e386cd" + item_url = ( + "https://api.github.com/repos/Disservin/fast-chess/zipball/" + fastchess_sha + ) + print("Building fast chess from sources at {}".format(item_url)) - cutechess = "cutechess-cli" + EXE_SUFFIX - cutechess_path = testing_dir / cutechess + blob = requests_get(item_url).content - # Download cutechess-cli if missing or overwrite if there are issues. - if not verify_required_cutechess(cutechess_path): - download_cutechess(cutechess, testing_dir) - else: - os.chdir(curr_dir) - return True + tmp_dir = Path(tempfile.mkdtemp(dir=worker_dir)) + file_list = unzip(blob, tmp_dir) + prefix = os.path.commonprefix([n.filename for n in file_list]) + os.chdir(tmp_dir / prefix) + + cmd = "make -j USE_CUTE=true" + with subprocess.Popen( + cmd, + shell=True, + env=os.environ, + stderr=subprocess.PIPE, + universal_newlines=True, + bufsize=1, + close_fds=not IS_WINDOWS, + ) as p: + errors = p.stderr.readlines() + + if p.returncode: + raise WorkerException("Executing {} failed. Error: {}".format(cmd, errors)) - ret = True + shutil.move("fast-chess" + EXE_SUFFIX, testing_dir) + shutil.rmtree(tmp_dir) + os.chdir(worker_dir) - if not verify_required_cutechess(cutechess_path): + except Exception as e: print( - "The downloaded cutechess-cli is not working. Trying to restore a backup copy ..." - ) - bkp_cutechess_clis = sorted( - worker_dir.glob("_testing_*/" + cutechess), - key=os.path.getctime, - reverse=True, + "Exception downloading, extracting or building fast-chess:\n", + e, + sep="", + file=sys.stderr, ) - if bkp_cutechess_clis: - bkp_cutechess_cli = bkp_cutechess_clis[0] - try: - shutil.copy(bkp_cutechess_cli, testing_dir) - except Exception as e: - print( - "Unable to copy {} to {}. Error: {}".format( - bkp_cutechess_cli, testing_dir, str(e) - ) - ) - if not verify_required_cutechess(cutechess_path): - print( - "The backup copy {} doesn't work either ...".format( - bkp_cutechess_cli - ) - ) - print("No suitable cutechess-cli found") - ret = False - else: - print("No backup copy found") - print("No suitable cutechess-cli found") - ret = False - - os.chdir(curr_dir) - return ret + return verify_required_fastchess(testing_dir / fastchess) def validate(config, schema): @@ -821,7 +792,7 @@ def my_error(e): # Limit concurrency so that at least STC tests can run with the evailable memory # The memory need per engine is 16 for the TT Hash, 10 for the process 138 for the net and 16 per thread - # 60 is the need for cutechess-cli + # 60 is the need for fast-chess # These numbers need to be up-to-date with the server values STC_memory = 2 * (16 + 10 + 138 + 16) max_concurrency = int((options.max_memory - 60) / STC_memory) @@ -1516,8 +1487,8 @@ def worker(): if not verify_toolchain(): return 1 - # Make sure we have a working cutechess-cli - if not setup_cutechess(worker_dir): + # Make sure we have a working fast-chess + if not setup_fastchess(worker_dir): return 1 # Check if we are running an unmodified worker