Compare commits

...

699 Commits

Author SHA1 Message Date
Tomasz Sobczyk 9a4c7cf4e3 Add transform minimize_binpack for minimizing existing .binpack datasets. (#4447)
Takes advantage of the sample skipping rules that are used during training (capture, check, or VALUE_NONE).
Adds positions to keep continuity, which improves compression.
2023-04-25 19:21:29 +02:00
Linmiao Xu 8e16592430 Tools transform option for filtering data for training nn-335a9b2d8a80.nnue (#4324)
Append hash of first master net trained with filter method

Hardcode depth 6 and remove option to set depth

Underscores for consistency

Filter out standard startpos positions too
2023-02-05 14:21:48 +01:00
Linmiao Xu 073d71a36b [tools] Fix castling moves counting towards # captures in gather_statistics (#4283) 2022-12-12 22:55:39 +01:00
Tomasz Sobczyk 399d556c27 Minimal support for FRC in the data generator. (#4049)
Allows UCI_Chess960 to be true during data generation.
If UCI_Chess960 is true then strips castling rights from all saved
positions and skips saving positions with castling move.
UCI_Chess960 is respected in transforms.
2022-06-03 06:36:46 +02:00
Joost VandeVondele 6e907f52c5 Merge pull request #4040 from Sopel97/tools
[tools] Merge branch 'upstream/master' (4c7de9e8ab) into tools
2022-05-30 21:03:44 +02:00
Tomasz Sobczyk e87358c53d Narrow down CI to the most important subset.
The tools branch doesn't require as much compatibility as the main Stockfish project.
2022-05-30 18:02:36 +02:00
Tomasz Sobczyk f710dc97e2 Merge branch 'upstream/master' (4c7de9e8ab) into tools 2022-05-30 12:07:07 +02:00
Fabian Fichter c90279e156 Fix stalemate value in MCTS (#4015) 2022-05-08 21:57:18 +02:00
Joost VandeVondele 5640ad48ae Merge pull request #3707 from Sopel97/max_nodes
Prevent search explosion in data generation when search is bounded by node count.
2021-09-18 09:14:56 +02:00
Tomasz Sobczyk 5956efafdd Hard-kill search in generate_training_data when the node count is 3x over the limit. 2021-09-17 21:16:49 +02:00
Joost VandeVondele cfee179152 Merge pull request #3705 from Sopel97/fix_tsan_warn
Fix usage of sync_endl instead of endl causing UB mutex unlock.
2021-09-17 12:03:55 +02:00
Tomasz Sobczyk b165fa0e96 Fix usage of sync_endl instead of endl causing UB mutex unlock. 2021-09-17 09:36:27 +02:00
Joost VandeVondele f3c921c854 Merge pull request #3704 from Sopel97/nodes_multipv
Add a nodes bound for the multiPV search in "generate_training_data".
2021-09-15 23:42:17 +02:00
Tomasz Sobczyk 474b63754d Add a nodes bound for the multiPV search in "generate_training_data". 2021-09-15 23:31:35 +02:00
Joost VandeVondele f2dbb3f6c8 Merge pull request #3701 from Sopel97/time_limit
Add "max_time_*" options to "generate_training_data" tool.
2021-09-15 06:55:53 +02:00
Tomasz Sobczyk 79abe1e662 Add "max_time_*" options to "generate_training_data" tool that allow limiting the runtime by time instead of count. 2021-09-14 14:47:24 +02:00
Joost VandeVondele a02a6bf13e Merge pull request #3687 from Sopel97/fix_ply_init
Fix uninitialized ss->ply in data generator
2021-09-02 22:24:15 +02:00
Tomasz Sobczyk f8d1315d90 Fix uninitialized ss->ply in data generator 2021-09-02 21:31:28 +02:00
Joost VandeVondele 8fc7d9a4d4 Merge pull request #3645 from Sopel97/tools_merge
Update tools to 2021-08-09
2021-08-20 08:57:17 +02:00
Tomasz Sobczyk 2922bcc1a7 Merge remote-tracking branch 'upstream/master' into merge_tmp 2021-08-15 21:53:46 +02:00
Tomasz Sobczyk 5d99239e95 Remove old travis CI file 2021-08-15 21:50:28 +02:00
Tomasz Sobczyk 1deb64f0a7 Fix instrumentation 2021-08-15 21:50:21 +02:00
Tomasz Sobczyk 7586e49548 bump macos version to 10.15 2021-08-09 13:24:35 +02:00
Tomasz Sobczyk 2b42d3a55a remove werror 2021-08-09 13:17:38 +02:00
Tomasz Sobczyk cd26704ae0 fix mcts init 2021-08-09 13:09:14 +02:00
Tomasz Sobczyk d76be2f428 Merge branch 'tools' into tools_merge 2021-08-09 13:06:54 +02:00
Tomasz Sobczyk 368bd2e4f9 most-merge fixes 2021-08-09 13:01:52 +02:00
Tomasz Sobczyk 51b4e7bd6e Merge branch 'tools' into tools_merge 2021-08-09 11:39:42 +02:00
Stéphane Nicolet 3963e3de55 Clean up previous patch 2021-06-17 16:05:12 +02:00
Tomasz Sobczyk 9094255f50 Add primitive MCTS search. 2021-06-17 16:05:12 +02:00
Sergio Vieri a44b1115c4 Fix incorrect input option 2021-06-14 14:57:57 +08:00
Tomasz Sobczyk c5ed9d1d76 fix accumulator state initialization in set_from_packed_sfen 2021-06-12 20:32:10 +02:00
Tomasz Sobczyk cee4ed39bd fix accumulator state initialization in set_from_packed_sfen 2021-06-12 18:10:55 +02:00
Joost VandeVondele aff5cf9ef7 Merge pull request #3501 from vondele/mergeBinpacks
Add a tool to merge binpacks
2021-05-26 21:23:56 +02:00
Joost VandeVondele bf187c46c8 Add a tool to interleave binpacks
this tool with take N binpacks as input to produce 1 binpack as output.
The input binpacks are read in random order, with a probability related to their size,
but each input file is read sequentially. The output is thus an appropriately shuffled binpack.
The tool is much faster than cat'ing the files together followed by a shuffle.
It assumes that the input binpacks themselves have no particular internal ordering.
2021-05-26 21:23:01 +02:00
Joost VandeVondele 774c0caf12 Merge pull request #3498 from Sopel97/old_fix_again
Add additional checks for en-passant possiblity when fixing the erroneus ep flag from a fen.
2021-05-25 14:09:52 +02:00
Tomasz Sobczyk 55ce07b773 Add additional checks for en-passant possiblity when fixing the erroneus ep flag from a fen. 2021-05-24 23:22:48 +02:00
Joost VandeVondele ed8b381cce Merge pull request #3496 from Sopel97/fix_discrep
Fix discrepancy for ep square between set and move in the binpack lib.
2021-05-24 22:43:14 +02:00
Joost VandeVondele 911629a118 Merge pull request #3497 from Sopel97/validation_easty
Add dedicated command for training data validation.
2021-05-24 20:19:42 +02:00
Tomasz Sobczyk eac1d430b4 Add dedicated command for training data validation. 2021-05-24 19:43:36 +02:00
Joost VandeVondele 5676a50807 Merge pull request #3493 from Sopel97/tools
Merge tools branch with current master
2021-05-24 19:23:56 +02:00
Tomasz Sobczyk ca365f17ba Fix discrepancy for ep square between set and move in the binpack lib.
basically, the binpack lib doesn't reset the epsquare after f7f5 in this 5kb1/5p2/2B3p1/1N1KP2p/3p1P2/2bP2P1/5r2/8 b - - 0 1 position, but it does reset it when passed the fen 5kb1/8/2B3p1/1N1KPp1p/3p1P2/2bP2P1/5r2/8 w - f6 0 50. Potentially creating a discrepancy based on whether the position was set directly or arrived at by a move
2021-05-24 19:17:42 +02:00
Tomasz Sobczyk a4605860c6 Post-merge fixes. 2021-05-24 11:45:21 +02:00
Tomasz Sobczyk 127c1f2fe2 Merge branch 'master' into tools 2021-05-24 11:32:58 +02:00
Joost VandeVondele cbd72299c1 Merge pull request #3483 from Sopel97/stats2
Add more output to endgame stats.
2021-05-21 20:25:47 +02:00
Joost VandeVondele cb2877cc7c Merge pull request #3484 from Sopel97/outputfile
Add output_file option to gather_statistics.
2021-05-21 20:25:22 +02:00
Joost VandeVondele e1189b9bcf Merge pull request #3486 from Sopel97/removeensurequiet
Remove ensure_quiet parameter from generate_training_data.
2021-05-21 20:24:49 +02:00
Tomasz Sobczyk abb7fa00ab Remove ensure_quiet parameter from generate_training_data. 2021-05-21 11:18:36 +02:00
Tomasz Sobczyk c124d55fa6 Add more output to endgame stats. 2021-05-20 13:25:07 +02:00
Tomasz Sobczyk 0f241355da Add output_file option to gather_statistics.
It is optional. When specified it will also forward the final results output to the provided file.
2021-05-20 13:22:11 +02:00
Joost VandeVondele 733f22e7c2 Merge pull request #3478 from Sopel97/newtoolsstats
Additional statistics for gather_statistics
2021-05-19 15:59:45 +02:00
Tomasz Sobczyk dc00b6c188 Update docs 2021-05-19 13:52:18 +02:00
Tomasz Sobczyk 0a464a7c21 Improve material imbalance output 2021-05-19 13:51:40 +02:00
Tomasz Sobczyk f89f8bd8ee Add endgame configuration stats 2021-05-19 13:48:02 +02:00
Tomasz Sobczyk d664ae123f Update docs 2021-05-19 12:56:44 +02:00
Tomasz Sobczyk a4b598060c Add stats: ply_discontinuities, material_imbalance, results 2021-05-19 12:55:14 +02:00
Joost VandeVondele 640ec5706e Merge pull request #3475 from Sopel97/better_stats_out
Improve gather_statistics output structure.
2021-05-18 15:39:38 +02:00
Tomasz Sobczyk 8634a5d021 Improve gather_statistics output structure. 2021-05-18 15:31:56 +02:00
Joost VandeVondele 95f066785e Merge pull request #3472 from Sopel97/abort_on_unknown
Don't ignore unknown options, don't execute the command instead.
2021-05-17 12:20:43 +02:00
Tomasz Sobczyk ddcfaa06fa Don't ignore unknown options, don't execute the command instead. 2021-05-17 11:35:36 +02:00
Tomasz Sobczyk 201d324187 Add . as an additional include directory both for .depend and for the build. 2021-05-14 17:45:39 +02:00
Tomasz Sobczyk 2421a88a54 Post merge fixes 2021-05-13 11:03:05 +02:00
Tomasz Sobczyk 8f0dbc9348 Merge remote-tracking branch 'upstream/master' into tools_merge_20210513 2021-05-13 10:53:57 +02:00
Tomasz Sobczyk 6b24954738 Merge pull request #323 from Sopel97/tools_after_merge_2
Tools after merge 2
2021-04-19 19:06:51 +02:00
Tomasz Sobczyk c2511ffc7b Renaming and small changes. 2021-04-19 19:05:37 +02:00
Tomasz Sobczyk ba32bd5d70 Bring the changes closer to official-stockfish/master 2021-04-19 18:57:21 +02:00
Tomasz Sobczyk 19f712cdbb Post-merge fixes. 2021-04-18 20:33:49 +02:00
Tomasz Sobczyk 08e255960d Merge remote-tracking branch 'upstream/master' into data_generation 2021-04-18 19:45:46 +02:00
Tomasz Sobczyk f1d4c1c896 remove useless stuff 2021-04-18 19:24:23 +02:00
Tomasz Sobczyk 696e849a30 learn -> tools 2021-04-18 19:18:41 +02:00
Tomasz Sobczyk 8169de72e2 asd 2021-04-18 19:04:37 +02:00
Tomasz Sobczyk 3101ae7973 remove learn 2021-04-18 19:04:14 +02:00
Tomasz Sobczyk 17946c5954 Merge pull request #322 from fsmosca/tb-issue-6
Fix ranking of root moves by TB
2021-04-17 00:22:28 +02:00
fsmosca 744533c2cf Fix ranking of root moves by TB 2021-04-13 18:54:54 +08:00
fsmosca 44f4d6f617 Fix ranking of root moves by TB 2021-04-13 18:54:19 +08:00
fsmosca 8748fd49b3 Fix include path in tbprobe 2021-04-10 19:11:38 +09:00
fsmosca dfa53e4062 Fix some include paths in tbprobe 2021-04-10 19:11:38 +09:00
Tomasz Sobczyk 0b33978e02 Merge pull request #320 from Sopel97/fix_stats_docs
Fix stats.md docs.
2021-04-05 18:54:53 +02:00
Tomasz Sobczyk a93777c4ed Fix stats.md docs. 2021-04-05 18:54:31 +02:00
Tomasz Sobczyk ad24a8d2b4 Merge pull request #319 from Sopel97/more_stats
More stats
2021-04-05 18:51:27 +02:00
Tomasz Sobczyk 9dac979ce8 Update docs 2021-04-05 17:37:15 +02:00
Tomasz Sobczyk f8d9836ca3 Use an ordered container for the results. 2021-04-05 17:30:38 +02:00
Tomasz Sobczyk 1786be5553 Minor fixes 2021-04-05 17:30:36 +02:00
Tomasz Sobczyk e371d133a7 Fix grouping and do dedup in registry. 2021-04-05 17:25:28 +02:00
Tomasz Sobczyk e7b3803fd0 Add more counters 2021-04-05 17:00:27 +02:00
Tomasz Sobczyk fcd53684b6 To/from move stats 2021-04-05 16:43:25 +02:00
Tomasz Sobczyk b2a5bf4171 Deduplicate statistic gatherers. Fix King square counter compilation errors. 2021-04-05 16:36:27 +02:00
Tomasz Sobczyk eda51f19a2 Add king square counter 2021-04-05 16:15:37 +02:00
Tomasz Sobczyk 570a0f6f3c Per square stats utility 2021-04-05 16:12:47 +02:00
Tomasz Sobczyk 7d74185d0b Add max_count parameter to limit the number of positions read. 2021-04-05 14:21:25 +02:00
Tomasz Sobczyk f85dbc3fe3 Reorder code and add important comments. 2021-04-05 14:21:25 +02:00
Tomasz Sobczyk f69946cd0b Merge pull request #317 from fsmosca/3fold_rep_termination
Fix segfault and end the game by 3-fold repetitions
2021-04-05 12:39:21 +02:00
Tomasz Sobczyk 8144fc54fc Merge pull request #318 from Sopel97/revert_shit
Revert "Add additional checks for en-passant possiblity when fixing the erroneus ep flag from a fen."
2021-04-05 12:39:13 +02:00
Tomasz Sobczyk 8365109972 Revert "Add additional checks for en-passant possiblity when fixing the erroneus ep flag from a fen."
This reverts commit 6afcdaa928.
2021-04-05 12:37:11 +02:00
fsmosca 560daefb01 Update position.h
* Add is_fifty_move_draw() and is_three_fold_repetition for gensfen()
2021-04-05 13:31:49 +08:00
fsmosca f57af4d203 Update position.cpp
* Add is_fifty_move_draw() and is_three_fold_repetition for gensfen()
2021-04-05 13:31:21 +08:00
fsmosca 5bb6cdf7ba Update gensfen.cpp
* Terminate game by 3-fold repetition.
* Fix segmentation fault by properly initializing the random_multi_pv_depth.
2021-04-05 13:29:49 +08:00
Tomasz Sobczyk 6afcdaa928 Add additional checks for en-passant possiblity when fixing the erroneus ep flag from a fen. 2021-04-03 23:17:55 +09:00
Tomasz Sobczyk 876902070d Add optional warmup step for training.
Specified with `warmup_epochs`, uses `warmup_lr`.
The purpose is to put the net into a somewhat stable state so that the gradients are not as high during the early stages of the training and don't "accidentally" break the net.
2021-03-26 00:26:41 +09:00
Tomasz Sobczyk bbe338b9fc Add random move accuracy for comparison. 2021-03-25 22:06:46 +09:00
Tomasz Sobczyk 5fdb48a7cb Change some learn parameter naming. Update docs. 2021-03-14 22:15:16 +09:00
Tomasz Sobczyk 591609c262 Fix relation between halfmove and fullmove clocks. 2021-03-14 22:01:01 +09:00
QuackQuackBlah 03b888e118 Update gensfen_nonpv.md
Fixes typo.
2021-03-09 14:25:27 +09:00
Tomasz Sobczyk 0ddad45ab2 Add gather_statistics command that allows gathering statistics from a .bin or .binpack file. Initially only support position count. 2021-03-01 00:36:45 +09:00
Tomasz Sobczyk b68cd36708 http://talkchess.com/forum3/viewtopic.php?f=2&t=76736&p=885254#p885254 2021-02-28 23:28:12 +09:00
Tomasz Sobczyk 74774c36e1 Fix wrong multipv depth range. Fixes #291 2021-01-25 21:39:22 +09:00
Tomasz Sobczyk 1f7e5d3861 Add thread sanitized run for instrumented_learn and fix races. 2020-12-28 16:08:34 +09:00
Tomasz Sobczyk acf95c7c98 Accumulate clipping statistics to a 64 bit integer to prevent overflow for larger batch sizes. 2020-12-25 10:04:28 +09:00
Tomasz Sobczyk 1b560efabd Correctly handle the last batch of data in sfen_reader 2020-12-25 10:03:24 +09:00
Tomasz Sobczyk 6d28d97a91 Don't unload evalfile on set nnue false 2020-12-25 09:58:24 +09:00
Tomasz Sobczyk c1e69f450e Prevent q_ in loss calculation from reaching values that would produce NaN 2020-12-25 00:41:31 +09:00
Joost VandeVondele bb6188430d Add split_count argument to shuffle_binpack.py
this optional argument allows for splitting the input binpack in multiple output binpacks while shuffling.
2020-12-25 00:40:40 +09:00
Tomasz Sobczyk 4f6fdca31f Reduce the amount of sfens buffered for the validation step.
Used to be 10M, now we bound it by a multiple of validation_count, and at most 1M. This reduces the RAM usage greatly.
2020-12-25 00:17:35 +09:00
Tomasz Sobczyk 7636bcccd1 Correctly account for factors when computing the average absolute weight of the feature transformer. 2020-12-25 00:08:51 +09:00
Tomasz Sobczyk 2061be4730 smart_fen_skipping at gensfen_nonpv level 2020-12-24 21:37:30 +09:00
Tomasz Sobczyk 868b4e9421 add gensfen_nonpv docs 2020-12-24 21:37:30 +09:00
Tomasz Sobczyk 96b377a90a Add gensfen_nonpv 2020-12-24 21:37:30 +09:00
Tomasz Sobczyk 3f73c40412 More deterministic move accuracy validation. 2020-12-24 10:16:59 +09:00
Joost VandeVondele b50dcd7dde allow for repeated searches in rescoring
allows for repeating a depth N search K times.
Repeated searches improve the quality of eval, but don't bring in higher depth info.
Might allow for removing some of the noise in low depth scoring.
2020-12-24 09:46:10 +09:00
Tomasz Sobczyk 8ca82646a9 Use plain nnue eval for validation loss calculation instead of first performing qsearch 2020-12-22 10:35:19 +09:00
Tomasz Sobczyk 6853b4aac2 Simple filtering for validation data. 2020-12-22 09:40:25 +09:00
Tomasz Sobczyk 50df3a7389 fix annoying warning 2020-12-22 09:24:26 +09:00
Tomasz Sobczyk 994eb5e183 rescore_fen -> rescore. Make it work on .bin and .binpack inputs. 2020-12-21 10:48:20 +09:00
Tomasz Sobczyk ffae19b5a1 Add docs for rescore_fen 2020-12-21 10:48:20 +09:00
Tomasz Sobczyk a9cfaa4d98 Add a tool for rescoring fens from an epd file with fixed depth search 2020-12-21 10:48:20 +09:00
Tomasz Sobczyk f56613ebf6 Add 'validation_count' option for 'learn' that specifies how many positions to use for validation 2020-12-20 09:47:30 +09:00
Tomasz Sobczyk a7378f3249 Make next_fen in opening_book a critical section 2020-12-14 09:03:04 +09:00
Joost VandeVondele 76fbc5e3d0 Make score sign flip optional
Bug fix: flipping score is not needed for fishtest, make this optional.
2020-12-13 09:32:46 +09:00
kennyfrc f4b4430380 remove unnecessary makefile commands and fix blas on mac 2020-12-13 09:31:52 +09:00
Joost VandeVondele 9c65e868f9 Enhance pgn_to_plain.py
in case a score can be parsed from the comment field in the pgn, add it to the output.
This form works for the fishtest pgns, and is quite common (cutechess-cli among others).
2020-12-11 00:33:34 +09:00
Tomasz Sobczyk d99ba07b81 Fix incorrect enpassant flag for moves read from uci format in the binpack lib 2020-12-11 00:31:32 +09:00
Joost VandeVondele b49fd3ab30 Add -lstdc++fs to the link line of gcc
older versions of gcc (<8.1) need this, even if they accept -std=c++17

with this patch, the code can be run on fishtest again,
at least by the majority of workers (fishtest doesn't require c++17 to be available)

See e.g.
https://tests.stockfishchess.org/tests/view/5fcfbf801ac1691201888235

Bench: 3820648
2020-12-09 08:40:34 +09:00
nodchip ae045e2cd8 Merge pull request #258 from kennyfrc/stockfish-nnue-2020-08-30-macos
mac-compatible makefile with instructions for stockfish-nnue-2020-08-30
2020-12-09 08:39:36 +09:00
Kenn Costales 055f907315 Merge branch 'master' into stockfish-nnue-2020-08-30-macos 2020-12-08 22:49:11 +08:00
kennyfrc bb26ce5aa1 mac specific makefile with compilation instructions 2020-12-08 22:14:18 +08:00
Tomasz Sobczyk 3a1bd1185f Add binpack coarse shuffle tool. 2020-12-06 19:08:52 +09:00
Tomasz Sobczyk 28d6d7cb03 Avoid computing gradient for validation loss. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk fafb9557a8 Get train loss from update_parameters. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 4eb0e77a2a Store references instead of copying the results of intermediate autograd computations. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 6cd0b03098 Add some comments regarding the current state of autograd loss computation. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 99cb869db3 Reintroduce use_wdl. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk cf6bc7ecaf Cleanup around get_loss 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 256c4b55ec Properly apply gradient norm clipping after it's scaled in the update_parameters. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk de675e3503 Reintroduce optional scaling of the teacher signal. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 01ae7b1e2c Simplify passing constants that may vary between calls. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk cbd973fdaa Detect constant expressions in autograd and return 0 grad early. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk e975889132 Move cross_entropy calculation to a separate function. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 891abf5511 Make the autograd loss expression chain thread_local. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 8adf00ae6e Identify a single evalation chain by ID in autograd to prevent cache reuse for subsequent evaluations of the same expression tree. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk cb812c742c Add [[nodiscard]] attributes to autograd functions. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 26f19e1429 Make automatic differentiation node types constexpr. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk aec6017195 When forming an autograd expression only copy parts that are rvalue references, store references to lvalues. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk a5c20bee5b Apply gradient clipping. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk d103867558 Add memoization to the autograd expression evaluator. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk aa55692b97 Cross entropy loss. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 539bd2d1c8 Replace the old loss/grad calculation completely. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk b71d1e8620 Pass the new loss function to update_parameters 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 5a58eb803a Loss func with autograd 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 541fb8177a More utility in autograd. 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 6ce0245787 Basic autograd 2020-12-02 08:56:20 +09:00
Tomasz Sobczyk 1322a9a5fd Prevent false sharing of num_calls counter in the shared input trainer. Fix current_operation not being local to the executing thread. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 2aa7f5290e Fix comparison of integers with different signedness. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk a97b65eaef Fix compilation error with USE_BLAS 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 622e0b14c2 Remove superfluous example shuffling. Shuffling now only happens on reading. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 34510dd08a Remove used examples asyncronously. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 0bee8fef64 Don't unnecessarily copy the batch part. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk e954b14196 Prefetch weights for feature transformer backprop to shared cache. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 8009973381 Special case for alpha=1 in saxpy, slight performance increase. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 49b2dcb1f3 Preallocate memory for unique_features. Keep the training_features temporary buffer as a thread_local so we reuse the storage. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 1c8495b54b Remove handwritten saxpy because compilers optimize the second look anyway. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 15c528ca7b Prepare feature transformer learner. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk a3c78691a2 Prepare input slice trainer. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 401fc0fbab Prepare clipped relu trainer. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 774b023641 Add chunked for each with workers. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk cc11375f6d Skeleton for new evaluate learner 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 0d4b803b08 Prepare trainer affine transform. 2020-11-30 08:54:53 +09:00
Tomasz Sobczyk 4ea8572b6d Add single threaded sgemm. 2020-11-30 08:54:53 +09:00
nodchip ef4fdb40f9 Merge pull request #254 from noobpwnftw/merge
Merge
2020-11-28 09:03:38 +09:00
noobpwnftw 0b2ae6cb64 Merge remote-tracking branch 'remotes/official/master' into merge 2020-11-28 06:47:04 +08:00
Tomasz Sobczyk 92b14a5ba2 Add docs for transform. 2020-11-27 09:16:22 +09:00
Tomasz Sobczyk 89294e2e4f Add transform command. Add transform nudged_static subcommand. 2020-11-27 09:16:22 +09:00
nodchip c12848d5ac Merge pull request #249 from noobpwnftw/merge
Merge
2020-11-23 19:55:23 +09:00
Tomasz Sobczyk 45e3335ee8 Add missing docs. 2020-11-23 19:22:11 +09:00
Tomasz Sobczyk 9030020a85 Add smart_fen_skipping option to learn. 2020-11-23 19:22:11 +09:00
noobpwnftw f978e1bef0 Merge branch 'master' into merge 2020-11-23 13:07:31 +08:00
Tomasz Sobczyk ee13cfce67 Fix result assigned for a psvector when the positions are not continuous. 2020-11-23 08:32:08 +09:00
Tomasz Sobczyk 3cee6881ee Move the terminal position check to after qsearch, otherwise qsearch may end up in a terminal position. 2020-11-23 08:29:38 +09:00
noobpwnftw c29554a120 Merge remote-tracking branch 'remotes/official/master' into master
Bench: 3597730
2020-11-23 04:27:12 +08:00
Tomasz Sobczyk d43cd104b6 Fix uninitialized variable when searching from a terminal position. 2020-11-22 07:45:39 +09:00
JWmer 38d19eca14 Update instrumented.sh 2020-11-22 07:45:39 +09:00
JWmer 3975fc9c0d Update half_relative_ka.cpp 2020-11-22 07:45:39 +09:00
JWmer b0429237a8 Update half_ka.cpp 2020-11-22 07:45:39 +09:00
JWmer ea70e378cd Update a.cpp 2020-11-22 07:45:39 +09:00
JWmer 777c3a08ab Update README.md 2020-11-22 07:45:39 +09:00
JWmer f832aa6b6b Update evaluate.h 2020-11-22 07:45:39 +09:00
JWmer be4cd56146 Update half_kp.cpp 2020-11-22 07:45:39 +09:00
JWmer 021f47b00e Update half_relative_kp.cpp 2020-11-22 07:45:39 +09:00
JWmer 36c801699f Update k.cpp 2020-11-22 07:45:39 +09:00
JWmer 5b3e9b0eb3 Update p.cpp 2020-11-22 07:45:39 +09:00
JWmer c04c5b6658 Update nnue_common.h 2020-11-22 07:45:39 +09:00
JWmer b27c51b5cf Delete k-p-cr-ep_256x2-32-32.h 2020-11-22 07:45:39 +09:00
JWmer 72fee2f7a4 Delete k-p-cr_256x2-32-32.h 2020-11-22 07:45:39 +09:00
JWmer d9dcdc2b73 Delete k-p_256x2-32-32.h 2020-11-22 07:45:39 +09:00
Tomasz Sobczyk 5f18c88b3d Docs for book in gensfen. 2020-11-17 09:43:23 +09:00
Tomasz Sobczyk e1dbad47ce Add support for opening book to gensfen. 2020-11-17 09:43:23 +09:00
Tomasz Sobczyk d4350a16f3 Add representation of an opening book. 2020-11-17 09:43:23 +09:00
Tomasz Sobczyk d793663188 Add docs for max_grad option for learn 2020-11-16 10:08:56 +09:00
Tomasz Sobczyk 3dbc45bdfc Add gradient clipping. 2020-11-16 10:08:56 +09:00
Tomasz Sobczyk 50358e26c7 Fix searching terminal nodes in gensfen. 2020-11-15 22:18:13 +09:00
Tomasz Sobczyk 00bc80c3c4 Add assume_quiet option to the learner. 2020-11-15 22:18:13 +09:00
Tomasz Sobczyk 00797a3d86 add option ensure_quiet for gensfen that makes the generated position quiet 2020-11-15 22:18:13 +09:00
Tomasz Sobczyk 9b930023fb Fix default value for batchsize in learn docs. 2020-11-15 00:51:04 +09:00
Tomasz Sobczyk 691da3bdad Add more information for factorizers at the start of training. 2020-11-14 18:47:22 +09:00
Tomasz Sobczyk 4e1653d53a Fix reliance on transitive includes for factorizers in trainer feature transformer. Add a file that includes all factorizers. 2020-11-14 12:35:12 +09:00
Tomasz Sobczyk 69bc3ef9be Output loss more often. 2020-11-14 12:33:25 +09:00
Tomasz Sobczyk a71623f74c Add explicit read head seek to the start of the binpack file. Otherwise on MACOS the read head is placed at the end when app is specified. 2020-11-13 19:56:36 +09:00
Tomasz Sobczyk 2a8576b804 Fix compilation issues. 2020-11-10 10:21:09 +09:00
Tomasz Sobczyk 8069963c56 Update convert docs. 2020-11-10 10:21:09 +09:00
Tomasz Sobczyk 5d88e7bce8 Add optional move validation to training data conversion. No longer rely on static initialization order for magics initialization. 2020-11-10 10:21:09 +09:00
Tomasz Sobczyk 987b6c98d4 Move the observed feature collection to the threaded part now that it can be done safely. 2020-11-01 11:02:44 +09:00
Tomasz Sobczyk c53be1b23f Add specialized bitset for use in the trainer for observed features tracking. 2020-11-01 11:02:44 +09:00
Tomasz Sobczyk e8907bcfc4 Replace omp in trainer_feature_transformer 2020-10-31 11:54:03 +09:00
Tomasz Sobczyk db1b33d4ac Optimize trainer clipped relu propagate 2020-10-31 11:52:51 +09:00
Tomasz Sobczyk b5714c4084 Parallelize input slice trainer backprop. 2020-10-31 11:52:26 +09:00
Tomasz Sobczyk 941897ff2c Optimize trainer clipped relu backpropagate. 2020-10-31 11:50:12 +09:00
Tomasz Sobczyk c96743c5bd Optimize feature transformer backpropagation stats. 2020-10-31 11:49:29 +09:00
Tomasz Sobczyk 2c10b1babc Optimize feature transformer clipped relu. 2020-10-31 11:48:02 +09:00
Tomasz Sobczyk 7bedf6c5ab Specify the whole evalsave message because otherwise the first evalsave/0 triggers it. 2020-10-31 08:36:58 +09:00
Tomasz Sobczyk 8c81bbd3db Fix the counter in for_each_index_with_workers going out of scope before workers finish. 2020-10-31 08:36:58 +09:00
Tomasz Sobczyk a56d8124d8 Replace non-blas parts of trainers with our own blas-like routines. 2020-10-31 08:36:58 +09:00
Tomasz Sobczyk c56a4a36eb Add our own blas-like routines that use stockfish's thread pool for parallelization. 2020-10-29 23:57:51 +09:00
Tomasz Sobczyk ee0917a345 Pass ThreadPool to update_parameters, propagate, and backpropagate. 2020-10-29 09:21:19 +09:00
Tomasz Sobczyk f1e96cab55 Align trainer arrays to cache line. 2020-10-29 09:12:50 +09:00
Tomasz Sobczyk 8fac468259 Add a cache line aligned allocator. 2020-10-29 09:12:50 +09:00
Tomasz Sobczyk ec9e49e875 Add a HalfKA architecture (a product of K - king, and A - any piece) along with all required infrastructure. HalfKA doesn't discriminate kings compared to HalfKP. Keep old architecture as the default one. 2020-10-29 09:10:01 +09:00
Tomasz Sobczyk 317fda2516 Cleanup eval saving and lr scheduling. 2020-10-28 23:08:05 +09:00
Tomasz Sobczyk 680654b254 Add dots to output every epoch for progress visualization. 2020-10-28 09:36:43 +09:00
Tomasz Sobczyk f81fa3d712 Replace global_learning_rate with learning_rate local to the learner and passed to update_parameters as a parameter. 2020-10-28 09:36:07 +09:00
Tomasz Sobczyk cde6ec2bf2 Make all grad related functions in learn static. Pass calc_grad as a parameter. 2020-10-27 14:47:50 +09:00
Tomasz Sobczyk e4868cb59e Move setting learn search limits to learner. 2020-10-27 14:47:07 +09:00
Tomasz Sobczyk c229929d26 Remove the position parameter from learn. 2020-10-27 00:35:43 +09:00
Tomasz Sobczyk a8066cd4a9 Rename elmo lambdas 2020-10-27 00:33:58 +09:00
Tomasz Sobczyk f7de49eb66 Create a collective parameter struct for learner. 2020-10-27 00:33:58 +09:00
Tomasz Sobczyk ba390a7f9a Print the used factorizer when intializing training. 2020-10-27 00:32:39 +09:00
Tomasz Sobczyk e01397c674 Remove multi_think 2020-10-26 19:40:40 +09:00
Tomasz Sobczyk e515f1f61f Move SfenWriter to a separate file 2020-10-26 19:39:58 +09:00
Tomasz Sobczyk 65e443954a Update expected gensfen finished responses. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk 03abfae41f Reorder members, renaming. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk 6d4d20c4be Cleaner printing and some renaming. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk d77b3d176e Always flush sfen writer at the end of gensfen and when it is destroyed. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk 21fac7c53c A collective struct for gensfen parameters. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk cb61dc9c9b Make sfen writer a part of gensfen. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk 3f289546da Make some gensfen members private. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk 821b655bc6 Move gensfen progress reporting from sfen writer to gensfen 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk af238fe132 Rewrite gensfen to use stockfish's thread pool. 2020-10-26 09:37:59 +09:00
Tomasz Sobczyk 0e528995c2 Print avg bias/weight for affine trasform and feature transformer during training. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk fe766f4f42 Additional output from layers during training. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk 2c477d76ec Cleaner and more outputs during training initialization. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk b882423005 Bring back info for finished evalsave. Update tests with the new message. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk 4b72658409 Synchronize printed info regions in the learner and sfen reader. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk d824bd8ec5 Add an overload for io manip in the logger. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk 54dd6a2407 Add logger with synchronized regions. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk cf3edfed82 Improve info messages. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk c49ae541c4 Add layer info for check_health. Print subsequent infos from the same scope with "-->" instead of "INFO:" for clarity. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk 8ddef320e6 Print an additional new line before calc_loss progress instead of after check_health in the feature transformer layer. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk d70408f204 Add docs entry for the verbose flag. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk a351c1d65e Add verbose flag to learn. Only print update parameters info when vebose=true 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk ec436d3dfd Print some weight update stats 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk be3937c37b Print layers and their indices during training initialization. 2020-10-25 22:18:28 +09:00
Tomasz Sobczyk 3bf397a569 Update instrumented_learn for the current codebase. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk 47a82bfc91 Document new options. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk 371acaa0b5 Allow changing sfen reader buffer sizes for the learn command. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk d31169bab5 Update CI to use epochs instead of loops. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk 8fb208598b pass shuffle flag in the constructor 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk 31f94a18b3 Update readme and docs after change from loop to epochs. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk fc3788f630 Use cyclic sfen reader for learning, change loop option to epochs. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk ad3d1b42e4 Make sfen reader only stop when it's destroyed. Now it is fully RAII. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk c58aa9696a Start sfen reader worker thread in the constructor. 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk 0636e1256d Add cyclic mode to the sfen reader. Make sfen reader take all files at construction 2020-10-25 19:22:56 +09:00
Tomasz Sobczyk e4a38c18dd Don't test syzygi 2020-10-24 08:52:42 +09:00
Tomasz Sobczyk e4e9f7e39b Reduce bench depth for testing with valgrind to prevent timeouts in CI. 2020-10-24 08:52:42 +09:00
Tomasz Sobczyk c7ac3688a7 Move the old convert stuff from learn to their own commands. 2020-10-24 08:52:42 +09:00
Tomasz Sobczyk f7530de20d Fix assertion in trainer 2020-10-23 09:35:41 +09:00
Tomasz Sobczyk 9564a52523 Remove whole file shuffling as it does not change learning behaviour, only works for bin, and is considered harmful for binpack. 2020-10-23 09:33:20 +09:00
Tomasz Sobczyk 7b4a769cca Fix base_dir not being applied to singular filenames. 2020-10-22 20:01:55 +09:00
Tomasz Sobczyk af138d1937 Fix crashes when trying to open a file of unknown type. Increase robustness of error handling. 2020-10-22 20:01:55 +09:00
Tomasz Sobczyk 886467e09f Fix crash when trying to read a non existing .binpack file. 2020-10-22 20:01:55 +09:00
Tomasz Sobczyk 11b28ad3b5 Don't treat unknown options in learn as file names. Add targetfile to specify individual files. 2020-10-22 20:01:55 +09:00
Tomasz Sobczyk 8f3e64a6d5 move sfen reader to separate file 2020-10-22 10:42:28 +09:00
Tomasz Sobczyk ff06d1e0ad Rewrite learner to be based on stockfish's thread pool. Reduce coupling along the way 2020-10-21 18:17:34 +09:00
Tomasz Sobczyk f2ad307de3 Clarify the behaviour of execute_with_worker[s] 2020-10-20 19:19:32 +09:00
Tomasz Sobczyk 74af287637 Fix execute_with_workers test call in uci 2020-10-20 19:19:32 +09:00
Tomasz Sobczyk 71862e2ebb remove incorrect move in execute_with_workers 2020-10-20 19:19:32 +09:00
Tomasz Sobczyk fd229c0768 Fix races and UBs 2020-10-20 19:19:32 +09:00
Tomasz Sobczyk 97fb9a89e4 allow waiting for task completion. 2020-10-20 19:19:32 +09:00
Tomasz Sobczyk 5188c26b20 Allow execution of tasks on the global thread pool. 2020-10-20 19:19:32 +09:00
Tomasz Sobczyk 146a6b056e PascalCase -> snake_case for consistency with the rest of the codebase. 2020-10-19 18:37:23 +09:00
Tomasz Sobczyk 2398d34e87 Move string split to misc 2020-10-19 08:29:51 +09:00
Tomasz Sobczyk 69ea3d30b2 Move the extra new line to after check health. 2020-10-19 08:29:51 +09:00
Tomasz Sobczyk 9023edc3c8 Add missing includes. 2020-10-19 08:29:51 +09:00
Tomasz Sobczyk 77624addf2 Cleanup last ".." in include paths. 2020-10-19 08:29:51 +09:00
Tomasz Sobczyk 497f689aa3 Cleanup nnue 2020-10-19 08:29:51 +09:00
Tomasz Sobczyk c286f9cd7d Cleanup trainer. 2020-10-19 08:29:51 +09:00
Tomasz Sobczyk ea8eb415de Cleanup trainer features. 2020-10-18 22:24:24 +09:00
Tomasz Sobczyk 3041adb080 Cleanup layers. 2020-10-18 19:32:15 +09:00
Tomasz Sobczyk 0d4c3014ca Cleanup features. 2020-10-17 23:19:16 +09:00
Tomasz Sobczyk ca760c3a5b Cleanup architecture files. 2020-10-17 20:01:09 +09:00
Tomasz Sobczyk c93f8732bf Force Use NNUE to pure when learning. 2020-10-17 08:44:38 +09:00
Tomasz Sobczyk 3cf193a90e Properly handle cases in verify and init when SkipLoadingEval is set. 2020-10-17 08:44:38 +09:00
Tomasz Sobczyk 5db46d0c82 Verify whether there is a network being used during training. 2020-10-17 08:44:38 +09:00
Tomasz Sobczyk e503cc4ea8 Add one more empty line between progress reports. 2020-10-17 00:13:50 +09:00
Tomasz Sobczyk 5856237e3f Rename hirate to startpos 2020-10-16 09:07:02 +09:00
Tomasz Sobczyk 904adb9a32 Indentation consistency in learn folder 2020-10-15 22:11:31 +09:00
Tomasz Sobczyk 880d23af1c Move sfen input/output streams to sfen_stream.h 2020-10-15 20:37:03 +09:00
Tomasz Sobczyk 14f83ad7b9 Move public search/qsearch interface from namespace Learner to namespace Search 2020-10-15 20:37:03 +09:00
Tomasz Sobczyk 0494adeb2c Move nnue evaluation stuff from evaluate.h to nnue/evaluate_nnue.h 2020-10-15 20:37:03 +09:00
Tomasz Sobczyk 4a340ad3b2 Add docs for auto_lr_drop 2020-10-12 23:56:26 +09:00
Tomasz Sobczyk 4a2bf16b30 Add option "auto_lr_drop" that specifies the amount of positions from previous lr drop after which to reduce lr by newbob_decay. 2020-10-12 23:56:26 +09:00
Tomasz Sobczyk 7d62b3f799 Store additional bits for fullmove clock and 50 more rule halfmove clock at the end of the bit stream. This change keeps backwards compatibility. 2020-10-11 20:59:27 +09:00
Tomasz Sobczyk de20887e11 Update readme. Link to docs. 2020-10-10 14:37:51 +09:00
Tomasz Sobczyk 2af4bf7eac Move the docs folder one above, it was in src by mistake. 2020-10-10 14:37:51 +09:00
Tomasz Sobczyk ef57ac78a3 Print gensfen speed when outputting status. 2020-10-09 08:14:48 +09:00
Joost VandeVondele adddf339bb Output sfens/second in the trainer, to track performance more easily 2020-10-08 08:29:42 +09:00
Tomasz Sobczyk 3f55b3af42 Change some gensfen parameter defaults. 2020-10-07 23:09:33 +09:00
Tomasz Sobczyk 8830209125 Change some learn parameter defaults. 2020-10-07 23:09:33 +09:00
Tomasz Sobczyk 2e57f3fa22 Switch to set recommended learn UCI options 2020-10-07 23:09:33 +09:00
Tomasz Sobczyk d1c44dca04 Switch to set recommended gensfen UCI options 2020-10-07 23:09:33 +09:00
Tomasz Sobczyk 5fa28b12fa Allow setting UCI options programmatically. 2020-10-07 23:09:33 +09:00
Tomasz Sobczyk 80cbc3ffee Fix grammar and spelling. Add recommendations for UCI options. 2020-10-07 16:08:26 +09:00
Tomasz Sobczyk 31f9d66f12 Initial documentation for learn, gensfen, convert, and binpack. 2020-10-07 16:08:26 +09:00
noobpwnftw 91cb4a6770 Skip eval dampening in Use NNUE = pure case 2020-10-03 19:31:21 +09:00
noobpwnftw 6f7a228707 Minor cleanups
Remove unused code and magic numbers
2020-10-01 16:52:20 +09:00
Joost VandeVondele f848d67341 Use fair scheduling of threads under valgrind
fixes some rare case where the master search thread makes no progress,
observed in CI.
2020-10-01 09:36:44 +09:00
nodchip b889debfb5 Merge pull request #171 from noobpwnftw/sf_merge
Merge SFdev
2020-09-30 10:18:41 +09:00
noobpwnftw d865159bd6 Fix variable initialization in test commands 2020-09-29 17:30:08 +08:00
noobpwnftw a8b502a975 Merge remote-tracking branch 'remotes/origin/master'
Bench: 3618595
2020-09-29 17:09:14 +08:00
noobpwnftw b44d539c94 Fix a bug that LR is not correctly scaled when initial LR is not 1.0 2020-09-29 16:18:42 +09:00
noobpwnftw 9d4bf4fe0c Optimize accumulators for null move. 2020-09-27 09:39:16 +09:00
noobpwnftw 96a3180770 Update instrumented_learn.sh
Fix typo.
2020-09-27 09:32:26 +09:00
noobpwnftw 5e8a49f7f2 Restore lambda and gradient function post-merge and minor fixes.
bench: 3788313
2020-09-26 12:55:02 +09:00
nodchip d1967bb281 Merge pull request #165 from Sopel97/merge_attempt
Merge attempt with official-stockfish/master and noobpwnftw/trainer
2020-09-26 10:05:16 +09:00
Tomasz Sobczyk c99541828f Remove the re-search on depth 0. It is correctly handled by search now. 2020-09-25 16:06:33 +02:00
Tomasz Sobczyk b6e7733b4c In gensfen call search before get_current_game_result so that rootMoves is initialized by Learner::init_for_search. Don't call Tablebases::rank_root_moves in get_current_game_result because it's called in Learner::init_for_search. This fixes accessing uninitialized variables related to tablebases. 2020-09-25 15:04:21 +02:00
Tomasz Sobczyk 89eeb36835 Initialize Tablebases::MaxCardinality to 0 to prevent uninitialized variable read in rank_root_moves 2020-09-25 13:42:27 +02:00
Tomasz Sobczyk 654b94f0a7 Remove old unused use_raw_nnue_eval option from gensfen tests 2020-09-25 10:41:40 +02:00
Tomasz Sobczyk 0a3e070ffb Adjust instrumented learn test for parameter changes. 2020-09-25 00:11:24 +02:00
Tomasz Sobczyk 9955f51215 Update bench signature. Bench: 4698761 2020-09-24 23:23:37 +02:00
Tomasz Sobczyk baf8b5beaf Change default net so that the architecture matches the architecture expected by the binary. 2020-09-24 22:58:21 +02:00
Tomasz Sobczyk d4a5f91766 Add info string when loading/failing to load an eval file. 2020-09-24 22:57:55 +02:00
Tomasz Sobczyk 9f87282c6d Fix net not being downloaded on build. Make PGO build faster by reverting gensfen command change. 2020-09-24 21:59:25 +02:00
Tomasz Sobczyk 9f3de8b40e Revert some unwanted changes from merge conflict resolution. 2020-09-24 21:10:10 +02:00
Tomasz Sobczyk 56f1a2fe49 Merge branch 'trainer' into merge_attempt 2020-09-24 20:45:23 +02:00
Tomasz Sobczyk 4abe836896 Merge remote-tracking branch 'upstream/master' into merge_attempt 2020-09-24 20:34:29 +02:00
noobpwnftw 9827411b7c Merge remote-tracking branch 'remotes/nodchip/master' into trainer 2020-09-24 21:45:28 +08:00
noobpwnftw 5be8b573be Merge remote-tracking branch 'remotes/origin/master' into trainer 2020-09-23 19:02:27 +08:00
noobpwnftw 411adab149 Merge remote-tracking branch 'remotes/nodchip/master' into trainer 2020-09-23 18:29:30 +08:00
Joost VandeVondele 2931463d3a Revert earlier TB changes.
they were not correct. Unfortunately, also restores the race on RootInTB
2020-09-21 09:45:42 +09:00
Tomasz Sobczyk d4737819cd Fix castling rights feature encoding. 2020-09-20 20:10:03 +09:00
Joost VandeVondele da28ce3339 Add initialization also to learning patch
fixes https://github.com/nodchip/Stockfish/issues/160
2020-09-20 08:16:54 +09:00
Joost VandeVondele 61bc8d12d3 Fix some races in learning
declare a few variables atomic.

Other races remain...
2020-09-19 10:09:30 +09:00
Joost VandeVondele e8472b5fbe Fix races in gensfen as detected with thread sanitizer.
RootInTB was an incorrectly shared global, probably leading to wrong scoreing

Minor:
 setting TB global state from input by all threads (all threads write same values)
 setting Limits global state by all threads (idem)
 thread counting for finalization

CI can be enabled once races are fixed in the learner, manually goes like:
```
make clean && make -j2 ARCH=x86-64-modern sanitize=thread    optimize=no debug=yes build
../tests/instrumented_learn.sh --sanitizer-thread
```

Needs some review.
2020-09-19 10:08:44 +09:00
noobpwnftw 26f63fe741 Merge remote-tracking branch 'remotes/origin/master' into trainer 2020-09-19 03:38:37 +08:00
noobpwnftw a47a3bfc7c Merge remote-tracking branch 'remotes/nodchip/master' into trainer 2020-09-19 02:14:17 +08:00
Tomasz Sobczyk 184bde47dc Add "seed" option to gensfen and learn 2020-09-16 23:09:45 +09:00
Tomasz Sobczyk efca5d561f More PRNG seeding options 2020-09-16 23:09:45 +09:00
Tomasz Sobczyk bc9be5a71f Allow setting PRNG seed 2020-09-16 23:09:45 +09:00
nodchip 6ae09ba266 Fixed a bug that the root color is wrong. 2020-09-16 12:10:35 +09:00
Joseph Ellis d160436921 Update description for PruneAtShallowDepthOnPvNode 2020-09-16 10:01:09 +09:00
nodchip ea5d437dbb Merge pull request #143 from Sopel97/no_eval_learn
Remove EVAL_LEARN and *learn* targets.
2020-09-14 14:37:50 +09:00
nodchip f579beec5d Merge pull request #150 from vondele/CIconvertPlain01
add convert_plain to CI
2020-09-14 08:19:23 +09:00
Joost VandeVondele 5d088e02c8 add convert_plain to CI 2020-09-13 19:16:27 +02:00
Tomasz Sobczyk 30a1bc4c64 Change default value of "PruneAtShallowDepthOnPvNode" so that the bench matches master. 2020-09-13 14:19:30 +02:00
Tomasz Sobczyk 89f38c938b Don't prompt when the training data file doesn't exist when trying to delete it 2020-09-13 13:52:42 +02:00
Tomasz Sobczyk 2e2de7607b Add extension to the PGO_TRAINING_DATA_FILE so that the generated file name matches the one we try to delete. 2020-09-13 13:47:19 +02:00
Tomasz Sobczyk e4a4f4001f parametrize the name of the training data file generated during pgo 2020-09-13 13:44:19 +02:00
Tomasz Sobczyk 9ee8ce67bf Move removal of generate training data file to profileclean. 2020-09-13 13:42:13 +02:00
Tomasz Sobczyk bd434b80c6 debug=yes for last CI test 2020-09-13 13:40:56 +02:00
Tomasz Sobczyk 0f270f7cbf Merge branch 'master' into no_eval_learn 2020-09-13 13:36:16 +02:00
Tomasz Sobczyk fb877c2c3e Add some building instructions to readme. 2020-09-13 12:14:35 +02:00
nodchip f27c72081b Merge pull request #147 from nodchip/EvalDir-2020-09-13
Fixed a bug that an assertion fails in the trainer.
2020-09-13 17:32:44 +09:00
nodchip cdfa71fa8e Merge branch 'master' into EvalDir-2020-09-13 2020-09-13 17:32:29 +09:00
Joost VandeVondele 3ea2d5ef61 Remove use of non-existent EvalDir option.
additionally allow all options to be converted to string.
Without this, restoring of the options (multi_think.cpp:117) can't work.

fixes https://github.com/nodchip/Stockfish/issues/128

Now gensfen/learn pass with debug=yes in CI
2020-09-13 16:38:21 +09:00
nodchip a94a076e39 Fixed a comment. 2020-09-13 16:35:52 +09:00
nodchip 1c84da9caa Fixed a bug that an assertion fails in the trainer. if the SkipLoading is false.
Fixes #128
2020-09-13 16:32:01 +09:00
nodchip 0a5893d337 Update README.md
Updated description according to recent option changes.
2020-09-13 14:05:52 +09:00
Matthies 50b4ff8354 Add missing include to make MSVC compile 2020-09-13 09:59:20 +09:00
Tomasz Sobczyk 4b70f4bf23 Add extra ld flags to the proper variable. 2020-09-13 02:07:29 +02:00
Tomasz Sobczyk 72164ba59c Add missing -fopenmp LDFLAG 2020-09-13 02:06:33 +02:00
Tomasz Sobczyk fbae6604b1 Remove LEARNCXXFLAGS, LEARNLDFLAGS, BLASDEFINE, BLASCXXFLAGS, BLASLDFLAGS in favor of directly modifying CXXFLAGS and LDFLAGS. 2020-09-13 00:18:01 +02:00
Tomasz Sobczyk f049c4776a Add tests in CI to cover compilation of both blas=no and blas=yes. 2020-09-12 21:19:15 +02:00
Tomasz Sobczyk 8d1ad6fbf6 Add a makefile option to enable use of BLAS. Default to "no" 2020-09-12 21:16:27 +02:00
Tomasz Sobczyk a6b02a61b7 Remove 32 bit builds. 2020-09-12 18:22:09 +02:00
Tomasz Sobczyk 9d84af11fe Remove remaining learn builds from CI. No replacement needed. 2020-09-12 18:20:21 +02:00
Tomasz Sobczyk 1da452029b Update travis to use build target instead of learn. 2020-09-12 16:27:35 +02:00
Tomasz Sobczyk 1e2fca4040 Move learn target to build target and profile-learn to profile-build. 2020-09-12 16:23:49 +02:00
Tomasz Sobczyk d33e7a9b07 Remove conditional compilation on EVAL_LEARN 2020-09-12 16:19:24 +02:00
Joost VandeVondele 8d499e6efa Fix flags for dependency generation (98f24570ab) 2020-09-12 22:25:36 +09:00
Joost VandeVondele 580b09381b Add a learning command to CI
fixes a small issue, with ponder

Probably the learning command can be improved a bit, so that despite the limited data, the code coverage is better.
2020-09-12 22:25:36 +09:00
Tomasz Sobczyk bcfe28b2ae Fix compilation of sfen_packer.cpp in debug. 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk 3388c22d71 Fix incorrect use of UCI::Option of type "combo". 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk 98f24570ab Add src to include paths, remove non-standard ".." in includes in learn directory. 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk 3c87d4fa9b "Fix" warning when memsetting Position 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk 96fa8fa8dc Add missing files. 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk a059fa86c4 Move sfen_packer to learn. 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk 683c6146ce Move declarations around and split them. 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk c6f5f6a082 Replace "use_raw_nnue_eval" with an uci option "Use NNUE pure" 2020-09-11 21:53:17 +09:00
Tomasz Sobczyk c76bb34a96 Add convert UCI function that allows conversion of files between any of plain, bin, and binpack. Usage: convert infile outfile [append]. 2020-09-11 09:36:24 +09:00
nodchip ac6e6f73f2 Added EnableTranspositionTable UCI option to enable/disable transposition table. 2020-09-11 09:20:48 +09:00
Tomasz Sobczyk 59402d4a6d Include <climits> for CHAR_BIT. Test both formats in instrumented learn. 2020-09-10 21:19:37 +09:00
Tomasz Sobczyk 7e6901af27 Remove unused immintring. Include intrin.h only on some platforms, otherwise builtins are used. 2020-09-10 21:19:37 +09:00
Tomasz Sobczyk 53ad4d8b56 A speculative build fix for linux. 2020-09-10 21:19:37 +09:00
Tomasz Sobczyk a7ca826593 MIT license/copyright notice in the library file. 2020-09-10 21:19:37 +09:00
Tomasz Sobczyk 585a5351bf Fix warnings. 2020-09-10 21:19:37 +09:00
Tomasz Sobczyk 6b76ebc2ca Support for binpack format in sfenreader in learner. Automatically detect file extension and choose the correct reader (bin or binpack) 2020-09-10 21:19:37 +09:00
Tomasz Sobczyk 020e66d2e6 Add "sfen_format" option in gensfen. Valid values are "bin" and "binpack". It determines the output format of the sfens. Binpack is a highly compressed formats for consecutive sfens. Extension is now determined by the used format, output_file_name should contain just the stem. 2020-09-10 21:19:37 +09:00
nodchip 1656e419bb Merge pull request #126 from nodchip/prune_at_shallow_depth_on_pv_node-2020-09-09
Set the value of prune_at_shallow_depth_on_pv_node on a UCI option callback
2020-09-10 10:44:23 +09:00
nodchip bb406a4492 Merge branch 'prune_at_shallow_depth_on_pv_node-2020-09-09' of github.com:nodchip/Stockfish into prune_at_shallow_depth_on_pv_node-2020-09-09
# Conflicts:
#	.travis.yml
#	src/search.cpp
2020-09-10 08:25:34 +09:00
nodchip 94f3cae760 Changed a sentence. 2020-09-10 08:23:21 +09:00
nodchip e63b6088ba Changed a option name more descriptive, "Training" -> "PruneAtShallowDepthOnPvNode". The default value was changed but the default behavior is not changed.
Changed to set a global option prune_at_shallow_depth_on_pv_node on a callback function.
2020-09-10 08:19:54 +09:00
nodchip 073d437384 Removed compile warnings. 2020-09-10 08:19:54 +09:00
nodchip 69563aeed9 Remove compile warnings. 2020-09-10 08:19:47 +09:00
Joost VandeVondele e0a9860708 Upgrade CI distro, remove special cases, fix one more warning 2020-09-10 08:15:15 +09:00
nodchip 005009f4e5 Changed a option name more descriptive, "Training" -> "PruneAtShallowDepthOnPvNode". The default value was changed but the default behavior is not changed.
Changed to set a global option prune_at_shallow_depth_on_pv_node on a callback function.
2020-09-09 23:38:00 +09:00
nodchip 9dcadfa642 Removed compile warnings. 2020-09-09 23:02:39 +09:00
nodchip 7bd4688747 Remove compile warnings. 2020-09-09 23:02:39 +09:00
noobpwnftw b3a0ded37a Merge remote-tracking branch 'remotes/nodchip/master' into trainer 2020-09-09 21:42:45 +08:00
nodchip d993bd36d0 Removed compile warnings. 2020-09-09 21:21:10 +09:00
noobpwnftw 84ba591118 Merge branch 'master' into trainer 2020-09-09 20:19:13 +08:00
nodchip 158399da4b Remove compile warnings. 2020-09-09 20:16:09 +09:00
Joost VandeVondele 8fcf8b97f1 Add -lstdc++fs 2020-09-09 19:40:43 +09:00
Joost VandeVondele 17d42e023e add more CI, instrumented runs 2020-09-09 19:40:43 +09:00
noobpwnftw 675d336ebb Merge branch 'master' into trainer 2020-09-09 16:08:49 +08:00
nodchip 4206a1edd0 Renamed parameters to avoid shadowing other parameters. 2020-09-09 10:26:42 +09:00
nodchip 1864845811 Commented out unused parameters. 2020-09-09 10:26:42 +09:00
nodchip 2583f68972 Removed macros for KPP evaluate functions. 2020-09-09 10:26:42 +09:00
nodchip cea17c92f9 Simplified evaluate_common.h. 2020-09-09 10:26:42 +09:00
nodchip 8d763fb503 Removed LEARN_GENSFEN_USE_DRAW_RESULT macro. 2020-09-09 10:26:42 +09:00
nodchip eafa569365 Removed macros for KPP factorization. 2020-09-09 10:26:42 +09:00
nodchip 5e25702672 Removed USE_TRIANGLE_WEIGHT_ARRAY macro. 2020-09-09 10:26:42 +09:00
nodchip f52165e1d3 Removed RESET_TO_ZERO_VECTOR macro. 2020-09-09 10:26:42 +09:00
nodchip dbad9d96e0 Removed LOSS_FUNCTION_IS_ELMO_METHOD macro. 2020-09-09 10:26:42 +09:00
nodchip ef1601218d Removed LOSS_FUNCTION_IS_CROSS_ENTOROPY_FOR_VALUE macro. 2020-09-09 10:26:42 +09:00
nodchip f52fbf8006 Removed LOSS_FUNCTION_IS_CROSS_ENTOROPY macro. 2020-09-09 10:26:42 +09:00
nodchip d37eb63581 Removed LOSS_FUNCTION_IS_WINNING_PERCENTAGE macro. 2020-09-09 10:26:42 +09:00
nodchip f3a158725d Removed SGD_UPDATE macro. 2020-09-09 10:26:42 +09:00
nodchip 0271d70775 Removed ADA_GRAD_UPDATE macro. 2020-09-09 10:26:42 +09:00
nodchip 05d26499b4 Removed LEARN_ELMO_METHOD macro. 2020-09-09 10:26:42 +09:00
nodchip 82dc68ba9f Removed #if for USE_GLOBAL_OPTIONS. 2020-09-09 10:26:42 +09:00
nodchip aa2452caf3 Removed #if for USE_EVAL_HASH. 2020-09-09 10:26:42 +09:00
nodchip ec96409176 Replaced DNDEBUG macro to _DEBUG macro. 2020-09-09 10:26:42 +09:00
nodchip 04a9a951b8 Removed "#if 0" and "#if 1". 2020-09-09 10:26:42 +09:00
nodchip 458771a181 Removed GENSFEN2019 macro. 2020-09-09 10:26:42 +09:00
nodchip 1d00d00241 Removed ENABLE_TEST_CMD macro. 2020-09-09 10:26:42 +09:00
nodchip 21cfead52c Removed unused OMP_ macro. 2020-09-09 10:26:42 +09:00
nodchip e6a6ba5221 Removed USE_BOOK macro. 2020-09-09 10:26:42 +09:00
nodchip a6013557f2 Removed EVAL_NNUE macro. 2020-09-09 10:26:42 +09:00
noobpwnftw d25657c439 Merge branch 'master' into trainer 2020-09-09 08:43:12 +08:00
noobpwnftw d21424c8d3 test 2020-09-09 07:31:22 +08:00
Tomasz Sobczyk 41b7674aee Improve comments, break long lines. 2020-09-08 20:07:30 +09:00
Tomasz Sobczyk 0202218f58 fix cast 2020-09-08 20:07:30 +09:00
Tomasz Sobczyk a0b2d6a01e Note a potential defect in sfen packer. 2020-09-08 20:07:30 +09:00
Tomasz Sobczyk 1482e5215a A second batch of code reorganization. 2020-09-08 20:07:30 +09:00
Tomasz Sobczyk 832c414b0d First batch of reorganization. 2020-09-08 20:07:30 +09:00
noobpwnftw 58863c3243 Update gensfen.cpp 2020-09-08 13:22:41 +09:00
Tomasz Sobczyk e5f05fa2b9 Add a script to extract a contiguous range of entries from a .bin file. 2020-09-08 09:31:53 +09:00
Joost VandeVondele 6e8f82ad76 Fix small CI failures
1) Only access UCI option if defined
2) disable -Werror for now.
3) disable a few target that don't have _mm_malloc.
4) Add profile-learn target, with small speedup.
5) just test on Linux + gcc (skip macOS, unclear openblas, skip linux+clang, unclear omp/std::filesystem).
2020-09-08 09:14:49 +09:00
Tomasz Sobczyk e638d66bbe Only add -s flag to the linker if debug=no 2020-09-08 09:10:58 +09:00
nodchip 4cc98d80f8 Replaced the utility function to create a directory to std::filesystem. 2020-09-07 18:56:41 +09:00
nodchip e004e47e5a Commented out an unused function parameter to remove a compile warning. 2020-09-07 16:21:40 +09:00
Joost VandeVondele bccc71afb4 fix openblas package name? 2020-09-07 16:16:08 +09:00
Joost VandeVondele 31e8be3008 First little CI step for the learner 2020-09-07 15:46:09 +09:00
Joost VandeVondele e9e52faae7 Typo fix 2020-09-07 15:21:50 +09:00
Joost VandeVondele edbbc1a4df Remove some warnings 2020-09-07 09:20:47 +09:00
Joost VandeVondele 3a06de298b Define BLAS variables in Makefile
makes it a little easier to change the BLAS library used,
doesn't hardcode the mingw headers. Works on Linux with openblas installed.
Should be no change on Windows.
2020-09-07 09:19:31 +09:00
Joost VandeVondele 3bf418e63f Fix some uninitialized variables with gensfen
fixes valgrind errors as seen with:

```
setoption name Use NNUE value true
isready
gensfen depth 6 loop 10 use_draw_in_training_data_generation 1 eval_limit 32000 output_file_name training_data/training_data.bin use_raw_nnue_eval 0
quit
```

the latter script now runs without valgrind errors on linux
2020-09-07 09:01:17 +09:00
Tomasz Sobczyk e9e6e47a93 Fix write_out_draw_game_in_training_data_generation flag not being respected. 2020-09-06 22:00:51 +09:00
Tomasz Sobczyk 0612adec41 Fix incorrect early exit in evaluate_leaf. 2020-09-05 08:43:34 +09:00
Tomasz Sobczyk 327e92aefe Remove trailing whitespaces. 2020-09-03 19:22:52 +09:00
Tomasz Sobczyk 2688194d44 Fix #91 2020-09-03 19:22:03 +09:00
Tomasz Sobczyk 9d5dc3d33f Fix compilation issues. 2020-09-03 19:21:27 +09:00
Tomasz Sobczyk c17f2b15fd General cleanup of learner.cpp. 2020-09-02 23:08:22 +09:00
nodchip 7d6668515c Added -static link option to the learn and profile-learn targets. 2020-08-30 14:54:07 +09:00
nodchip bc90567e09 Merge pull request #94 from nodchip/nnue-player-merge-2020-08-28
Nnue player merge 2020 08 28
2020-08-30 09:27:12 +09:00
nodchip d258662383 Update README.md 2020-08-29 09:12:10 +09:00
nodchip 9f2f31632c Fixed build errors. 2020-08-29 08:17:24 +09:00
nodchip f7bc4e6e45 Fixed compilation errors. 2020-08-29 00:56:05 +09:00
nodchip 906c18eb46 Merge branch 'master' of github.com:official-stockfish/Stockfish into nnue-player-merge-2020-08-28
# Conflicts:
#	README.md
#	src/Makefile
#	src/search.cpp
#	src/types.h
#	src/uci.cpp
#	src/ucioption.cpp
2020-08-28 11:26:11 +09:00
hero2017 5637996f79 Resolve #92
If we're defining something in a header then we should declare it.
2020-08-28 11:20:03 +09:00
nodchip def6ec4d16 Merge pull request #90 from tttak/wdl_20200824
Use winning_percentage_wdl in learn
2020-08-28 10:06:33 +09:00
tttak 03b43079eb Merge branch 'nodchip_master' into wdl_20200824 2020-08-28 09:16:07 +09:00
nodchip 763e72cc9f Changed the default value of use_raw_nnue_eval. Updated a source code comment. 2020-08-27 23:49:58 +09:00
nodchip 9fc3ff4c30 Added use_raw_nnue_eval option to return raw NNUE eval value in evaluate(). 2020-08-27 23:48:28 +09:00
tttak 4ce30d9522 Use winning_percentage_wdl in learn 2020-08-24 22:56:08 +09:00
nodchip 7ee8a2bbb7 Merge branch 'master' of github.com:nodchip/Stockfish 2020-08-22 12:19:46 +09:00
nodchip 87633b876c Added an option to convert teacher signals to winning probabilities. 2020-08-22 12:19:29 +09:00
nodchip 17d2b5bf17 Merge pull request #88 from tttak/convert_bin_from_pgn-extract_20200819
Modify convert_bin_from_pgn-extract
2020-08-21 23:34:39 +09:00
tttak f057aec4a9 Merge remote-tracking branch 'nodchip/master' into convert_bin_from_pgn-extract_20200819 2020-08-21 22:49:11 +09:00
nodchip e5e5d7d4ab Merge branch 'master' of github.com:nodchip/Stockfish 2020-08-21 21:17:40 +09:00
nodchip 11752d4e63 Added options to scale the scores in training data. #71 2020-08-21 21:16:55 +09:00
tttak aaa73b2569 modify convert_bin_from_pgn-extract 2020-08-19 22:47:22 +09:00
Serianol 2fb3f76399 Update Makefile 2020-08-17 20:50:45 +09:00
nodchip 2646080543 Merge branch 'master' of github.com:nodchip/Stockfish 2020-08-15 11:58:05 +09:00
nodchip 79654ac509 Added winning_probability_coefficient option to specify the coefficient to calculate the winning probability from a value. #71 2020-08-15 11:57:08 +09:00
tttak 69a95e431b update convert_bin
learn convert_bin in.txt output_file_name out.bin check_invalid_fen 1 check_illegal_move 1
convert in.txt ... done 16 parsed 3 is filtered (invalid fen:1, illegal move:2, invalid ply:0)
2020-08-15 00:27:52 +09:00
nodchip ee823afdad Deleted a duplicated Readme.md. 2020-08-14 23:33:28 +09:00
Joseph Ellis 430467db1c Create a UCI Training option
Creates a UCI Training option and uses it to disable LMP on PV nodes.
2020-08-13 08:32:10 +09:00
Joseph Ellis 44a54b63f1 Don't allow LMP on PvNodes
I mentioned this a while back in discord, but nothing seems to have ever come from it.  Anyway, to the best of my knowledge most current training data gen is being done at relatively low fixed depths.  With this in mind, the change to not allow LMP in PvNodes should result in a fairly significant increase in strength and reliability of the PV.
2020-08-13 08:32:10 +09:00
xXH4CKST3RXx e12a0cd9eb Update README.md
Additional instruction.
2020-08-12 10:33:39 +09:00
xXH4CKST3RXx c3224dd9a1 Update README.md
Typo
2020-08-12 10:33:39 +09:00
xXH4CKST3RXx 62228e6b18 Update README.md
Grammar, changed link.
2020-08-12 10:33:39 +09:00
nodchip 75b9d6f6b1 Fixed build parameters. 2020-08-11 16:37:47 +09:00
nodchip 35f04aaf24 Removed an unnecessary call for pos.is_draw(). 2020-08-10 19:42:39 +09:00
nodchip c420b327bf Added output messages. 2020-08-10 16:23:04 +09:00
nodchip 8c0429d1e5 Added detect_draw_by_insufficient_mating_material option. 2020-08-10 16:14:56 +09:00
nodchip 84070c02e6 Renamed use_game_draw_adjudication to detect_draw_by_consecutive_low_score. 2020-08-10 16:02:18 +09:00
nodchip 5467ba3c23 Renamed use_hash_in_training to skip_duplicated_positions_in_training. 2020-08-10 15:58:17 +09:00
nodchip 87c50c5cbc Renamed use_draw_in_validation to use_draw_games_in_validation.
Added comments.
2020-08-10 15:55:34 +09:00
nodchip fa5b2aec3a Renamed use_draw_in_training to use_draw_games_in_training. 2020-08-10 15:51:23 +09:00
nodchip a41cbb9ca9 Renamed use_draw_in_training_data_generation option to write_out_draw_game_in_training_data_generation. 2020-08-10 15:49:24 +09:00
nodchip 3bd3ef0aea Implemented the code to detect draw by insufficient mating material. 2020-08-10 15:47:11 +09:00
nodchip 4a87d7b787 Added the use_game_draw_adjudication option. 2020-08-10 15:44:58 +09:00
nodchip 12c6c2f550 Chagned to use the search value instead of the value of the PV leaf to avoid crash by assertion. 2020-08-10 13:07:22 +09:00
nodchip bac96aa04a Changed to use TB in the training data generator. #67 2020-08-10 12:17:26 +09:00
nodchip e65c515d6b Changed to specify the current tick as a random seed. #68 2020-08-10 12:09:21 +09:00
nodchip 643be3c6f9 Changed not to use std::random_device(). Because it always returns the same integers on MingW. #68 2020-08-10 10:45:03 +09:00
tttak 31d4f46f5e update convert_bin
learn convert_bin in.txt output_file_name out.bin check_illegal_move 1
convert in.txt ... done 16 parsed 4 is filtered (illegal fen:1, illegal move:2, illegal ply:1)
2020-08-10 09:53:52 +09:00
nodchip 53d15e5ec2 Merge pull request #79 from nodchip/nnue-player-merge
Merge Stockfish master to nodchip's repository
2020-08-10 09:51:44 +09:00
nodchip 4260ed0c7f Merge branch 'master' of github.com:official-stockfish/Stockfish into nnue-player-merge 2020-08-10 08:52:55 +09:00
nodchip 4f97d3446d Cleaned up source code. 2020-08-10 08:52:34 +09:00
nodchip 7f1f08d094 Merge branch 'master' of github.com:official-stockfish/Stockfish into nnue-player-merge
# Conflicts:
#	README.md
2020-08-09 09:19:47 +09:00
nodchip fcd70a3c81 Updated README.md.
Bench: 4067325
2020-08-08 21:00:19 +09:00
nodchip 3b5de9f18b Merge branch 'master' of github.com:official-stockfish/Stockfish into nnue-player-merge 2020-08-08 19:47:32 +09:00
nodchip 22b85810fe Re-added the code to skip loading a net file. 2020-08-08 19:04:08 +09:00
nodchip 4f94f29f39 Revert "Fixed a bug that the training data generation crashes if eval_limit is high."
This reverts commit b0d28ac3ab.
2020-08-08 18:38:02 +09:00
nodchip 9a0b20d3fc Changed to show if NNUE is used in the training data generator. 2020-08-08 18:24:09 +09:00
nodchip b0d28ac3ab Fixed a bug that the training data generation crashes if eval_limit is high. 2020-08-08 18:23:11 +09:00
nodchip 70d88364fe Fixed a bug that the training data generation crashes. 2020-08-08 18:22:29 +09:00
nodchip ed4d007e3c Fixed a bug that the training data generator crahses on memory allocation. 2020-08-08 18:21:38 +09:00
nodchip 2395833c07 Re-added commands for training data generator and trainer. 2020-08-08 16:52:18 +09:00
nodchip fa649ba1e2 Removed a compile warning. 2020-08-08 16:17:55 +09:00
nodchip 2c9075e919 Fixed Makefile to fix build. 2020-08-08 16:05:05 +09:00
nodchip 55a6b2bdc4 Merge branch 'master' of github.com:official-stockfish/Stockfish into nnue-player-merge
# Conflicts:
#	README.md
#	Readme.md
#	src/Makefile
#	src/evaluate.cpp
#	src/evaluate.h
#	src/misc.cpp
#	src/nnue/architectures/halfkp_256x2-32-32.h
#	src/nnue/evaluate_nnue.cpp
#	src/nnue/evaluate_nnue.h
#	src/nnue/features/feature_set.h
#	src/nnue/features/features_common.h
#	src/nnue/features/half_kp.cpp
#	src/nnue/features/half_kp.h
#	src/nnue/features/index_list.h
#	src/nnue/layers/affine_transform.h
#	src/nnue/layers/clipped_relu.h
#	src/nnue/layers/input_slice.h
#	src/nnue/nnue_accumulator.h
#	src/nnue/nnue_architecture.h
#	src/nnue/nnue_common.h
#	src/nnue/nnue_feature_transformer.h
#	src/position.cpp
#	src/position.h
#	src/types.h
#	src/ucioption.cpp
#	stockfish.md
2020-08-08 15:55:42 +09:00
nodchip 1abae04ceb Fixed Makefile. 2020-08-07 23:00:11 +09:00
nodchip bf7d02578e Fixed build errors. 2020-08-07 22:47:45 +09:00
nodchip 1c23465383 Moved the nnue folder. 2020-08-07 22:34:53 +09:00
nodchip c0e1235fef Added a description to Makefile. 2020-07-20 17:36:09 +09:00
No name 74049a450c Add NNUE targets to the output of 'make help' 2020-07-20 17:29:20 +09:00
nodchip fbdb373b64 Changed to set the binary directory to the current working directory. 2020-07-20 17:17:50 +09:00
mstembera 77018c77cc Fix profile builds for AVX512. 2020-07-19 21:25:50 +09:00
No name fd78fb05f6 Hide NNUE options if building without NNUE support
Also remove an unused option.
2020-07-19 20:17:01 +09:00
nodchip afd7d0ea4d Fixed a bug that Makefile specifies -mpopcnt for armv8-a. 2020-07-19 18:34:35 +09:00
nodchip 36092b855a Removed the x86-64-ssse3-popcnt architecture. 2020-07-19 14:17:35 +09:00
nodchip 3bbe4802b1 Removed the sse41-popcnt architecture. 2020-07-19 14:02:49 +09:00
nodchip c001a4e62d Revert "Removed x86-64-ssse3-popcnt and x86-64-sse41-popcnt."
This reverts commit 92c2167481.
2020-07-19 13:58:19 +09:00
No name 1536e31065 Load the parameter set on an `isready' as well
Unbreaks Scid vs. PC, which doesn't send `ucinewgame'.
2020-07-19 13:22:40 +09:00
nodchip 92c2167481 Removed x86-64-ssse3-popcnt and x86-64-sse41-popcnt. 2020-07-19 12:52:20 +09:00
nodchip a4786db4c2 Added support for architectures which supports SSE3+POPCNT, SSSE3+POPCNT and SSE41+POPCNT. 2020-07-19 12:41:50 +09:00
nodchip c24ad8d8b5 Supported sse3 build. 2020-07-19 12:26:37 +09:00
mstembera 961a4dad5c Add AVX512 support.
bench: 3909820
2020-07-19 12:07:28 +09:00
nodchip 7a13d4ed60 Changed the default eval file path so that more GUIs can use Stockfish NNUE. 2020-07-17 15:40:01 +09:00
xXH4CKST3RXx 4d4c80d7fd Update README.md
Added logo, reinforcement learning instructions, and resources list.
2020-07-17 12:40:47 +09:00
nodchip 2b821682aa Update README.md 2020-07-17 11:55:30 +09:00
xXH4CKST3RXx be754a2379 Update README.md 2020-07-16 13:21:14 +09:00
xXH4CKST3RXx ec5ef2b6df Update README.md 2020-07-16 13:21:14 +09:00
xXH4CKST3RXx df4da8dc41 Update README.md 2020-07-16 13:21:14 +09:00
xXH4CKST3RXx 6118151c66 Create README.md
Added and cleaned up Gekkehenker's training guide.
2020-07-16 13:21:14 +09:00
xXH4CKST3RXx 2fd1c48e60 Rename Readme.md to stockfish.md 2020-07-16 13:21:14 +09:00
No name 961047ed6e Experimental support for PGO builds of NNUE
Only 'nnue' target and only gcc/mingw.
(does not clean profile data generated by other compilers)
To use:
 make profile-nnue ARCH=arch
(see 'make help' for list of supported archs)
2020-07-16 08:53:03 +09:00
J. Oster e29499ee4b Use the path and filename for restoring parameter files. 2020-07-15 19:29:29 +09:00
nodchip 7f4b72cdfd Merge branch 'master' of github.com:nodchip/Stockfish 2020-07-13 22:25:56 +09:00
nodchip 686a5a0df9 Fixed a bug that gensfen command does not accept the use_draw_in_training_data_generation option. 2020-07-13 22:25:23 +09:00
Anson Hu df40de9486 game result bugfix 2020-07-12 15:56:03 +09:00
No name fcb391919f Disable EVAL_HASH for 'nnue' target
Gives a 7% speed gain for me, without any parameter set loaded
(all-zero).
2020-07-11 19:29:05 +09:00
nodchip ae4db5ebfd Merge pull request #45 from joergoster/sf-nnue-update
Sf nnue update
2020-07-11 19:17:38 +09:00
joergoster db0615eed9 Merge branch 'master' into sf-nnue-update 2020-07-11 12:03:17 +02:00
zz4032 d6e8089f50 Saving new network in correct path. 2020-07-11 17:46:43 +09:00
zz4032 b521e405d3 Default network path in Linux. 2020-07-11 00:07:15 +09:00
nodchip d7c358cf19 Fixed descriptions and sanity checks in Makefile. 2020-07-10 16:55:32 +09:00
nodchip bc6a8d09e9 Unified the nnue-learn and nnue-learn-use-blas targets into nnue-learn. 2020-07-10 16:17:35 +09:00
nodchip df05ecb1d5 Added halfkp_384x2-32-32. 2020-07-10 16:14:19 +09:00
nodchip 1de1eb2d0d Refactoring: Restructured the architecture list in Makefile. 2020-07-10 16:13:21 +09:00
No name 081761d084 Add support for SSSE3-only compiles
For Core 2 Duo.

To compile:
make ARCH=x86-64 ssse3=yes nnue

No observable difference in speed to SSE4.1 on my machine.
2020-07-10 15:21:50 +09:00
No name b9a32fe331 Define USE_SSE2 for any x86-64 target
Rather than only when popcnt=yes
x86-64 instruction set includes SSE2.
2020-07-10 14:56:33 +09:00
tttak a06234c639 enable convert_plain
learn convert_plain output_file_name xxx.txt xxx.bin
2020-07-09 09:51:00 +09:00
FireFather df9b2a87db Update misc.cpp
change name to Stockfish+NNUE
and add 3 more authors
2020-07-08 23:20:36 +09:00
FireFather 821aaf3836 Update misc.cpp
do not clutter console window
remove "Windows large pages not used."
only show message when/if successful
2020-07-08 23:20:36 +09:00
nodchip 272f0f88c3 Merge pull request #40 from FireFather/master
Update evaluate_nnue_learner.cpp
2020-07-08 20:52:39 +09:00
FireFather 76d124ed70 Update evaluate_nnue_learner.cpp
replace NNUE::kFileName with NNUE::fileName
2020-07-08 13:42:28 +02:00
FireFather ec3eaad64f update evaluate_nnue.cpp
rename kFileName and change to std:string
2020-07-08 11:59:18 +09:00
FireFather f7420652b7 UCI option EvalFile
Replace EvalDir with EvalFile
Can now browse filesystem for net (eval\nn.bin is default)
nn.bin no longer hard-coded
2020-07-08 11:59:18 +09:00
FireFather d1760a1f15 update evaluate_nnue.cpp
rename kFileName and change to std:string
2020-07-08 04:23:50 +02:00
FireFather c59583bbf0 UCI option EvalFile
Replace EvalDir with EvalFile
Can now browse filesystem for net (eval\nn.bin is default)
nn.bin no longer hard-coded
2020-07-07 23:25:20 +02:00
FireFather d61378cacb Merge pull request #1 from nodchip/master
Merge
2020-07-07 13:06:40 +02:00
joergoster cd55c268cb Bugfix.
Otherwise creating a new net fails.
2020-07-07 18:51:22 +09:00
joergoster a5af8510a5 Rework loading the net. 2020-07-07 15:13:59 +09:00
nodchip 288fdc5597 Added "nodes" option to the "gensfen" command to specify the number of the nodes to be searched. 2020-07-06 17:38:43 +09:00
nodchip 85c802d0b9 Revert "use winning_percentage_wdl in learn"
This reverts commit c964e902c5.

# Conflicts:
#	src/uci.cpp
2020-07-06 11:07:46 +09:00
nodchip 3b535b5ade Merge pull request #36 from tttak/WDL_20200703b
use winning_percentage_wdl in learn
2020-07-04 07:36:33 +09:00
nodchip cea5240909 Merge branch 'master' into WDL_20200703b 2020-07-04 07:36:16 +09:00
tttak 5dec3e547e merge "Provide WDL statistics"
https://github.com/official-stockfish/Stockfish/commit/110068808b51344ac59f8c6a0846f5dfdf670392
https://github.com/official-stockfish/Stockfish/pull/2778
https://github.com/official-stockfish/Stockfish/pull/2788
2020-07-04 07:35:15 +09:00
tttak c964e902c5 use winning_percentage_wdl in learn 2020-07-03 23:21:49 +09:00
tttak 9ce0ef3ac0 merge "Provide WDL statistics"
https://github.com/official-stockfish/Stockfish/commit/110068808b51344ac59f8c6a0846f5dfdf670392
https://github.com/official-stockfish/Stockfish/pull/2778
https://github.com/official-stockfish/Stockfish/pull/2788
2020-07-03 23:01:37 +09:00
FireFather 9c19021808 update translation
1 line src\eval\nnue\features\half_kp.cpp
1 line src\movegen.h
2020-07-03 09:24:15 +09:00
FireFather c679e8f360 Update search.h
1 line of translation
2020-07-03 09:24:15 +09:00
joergoster 13824d8b96 Explicitly specify SSE41. 2020-07-02 16:15:39 +09:00
joergoster 145e4c2a10 Add SSE41 switch.
This allows building modern compiles with SSE41 enabled,
which gives a nice speedup on my Bulldozer CPU.

For example:
make nnue ARCH=x86-64-modern sse41=yes -j
2020-07-02 16:15:39 +09:00
nodchip c8262f8aec Fixed a compile error. 2020-06-30 15:58:51 +09:00
tttak 486f72af54 バグ修正(learn convert_bin_from_pgn-extract) 2020-06-30 14:01:04 +09:00
tttak fda3945c07 learn convert_bin_from_pgn-extractコマンドを追加
http://rebel13.nl/download/data.html
Download Selected Lichess games
pgn-extract --fencomments -Wlalg --nochecks --nomovenumbers --noresults -w500000 -N -V -o comp-2019-06.txt comp-2019-06.pgn
stockfish.exe
learn convert_bin_from_pgn-extract pgn_eval_side_to_move 0 output_file_name fens_comp-2019-06.bin comp-2019-06.txt

https://github.com/glinscott/fishtest/wiki/PGN-files-of-games-played-on-fishtest
pgn-extract --fencomments -Wlalg --nochecks --nomovenumbers --noresults -w500000 -N -V -o fishtest.txt fishtest.pgn
stockfish.exe
learn convert_bin_from_pgn-extract pgn_eval_side_to_move 1 output_file_name fens_fishtest.bin fishtest.txt
2020-06-30 14:01:04 +09:00
FireFather 8f31d74cf6 More comment translation
including 11 files in /src
2020-06-30 00:45:32 +09:00
FireFather f5cc77bc7c EOL
add eol at eof
2020-06-29 08:27:41 +09:00
nodchip 9dc62809c8 Merge pull request #24 from FireFather/master
Comment translation
2020-06-28 11:43:53 +09:00
nodchip 123dd68452 Merge branch 'master' into master 2020-06-28 11:42:23 +09:00
nodchip f1a8580118 Merge branch 'master' of github.com:nodchip/Stockfish 2020-06-28 11:37:42 +09:00
nodchip 87c8b324f8 Simplified source code to estimate the winning ratio from an eval value.
We need to adjust the eta again after this commit is pushed.
2020-06-28 11:37:15 +09:00
nodchip 6324c2de75 Merge pull request #23 from joergoster/linux_sse41
Linux sse41
2020-06-28 10:29:14 +09:00
FireFather aea08de018 Translation
Files in /eval, /extra, & /learn - comments translated from Japanese to English
2020-06-28 03:12:55 +02:00
FireFather 2f8c692caa Merge pull request #2 from nodchip/master
merge
2020-06-28 01:37:01 +02:00
joergoster 96f2541191 Fix compilation under Linux with -DUSE_SSE41. 2020-06-27 19:41:13 +02:00
nodchip cb8accada2 Merge branch 'master' of github.com:nodchip/Stockfish 2020-06-27 22:19:33 +09:00
nodchip 13eb540020 Changed the formula to calculate winning ratio to 1/(1+10^(-Eval/4)). 2020-06-27 22:19:22 +09:00
joergoster 32c204fb56 Merge branch 'master' into sf-nnue-nodchip 2020-06-27 10:57:09 +02:00
tttak e229015127 learn convert_bin_from_pgn-extractコマンドを追加
http://rebel13.nl/download/data.html
Download Selected Lichess games
pgn-extract --fencomments -Wlalg --nochecks --nomovenumbers --noresults -w500000 -N -V -o comp-2019-06.txt comp-2019-06.pgn
stockfish.exe
setoption name SkipLoadingEval value true
isready
learn convert_bin_from_pgn-extract output_file_name fens_comp-2019-06.bin comp-2019-06.txt
2020-06-27 16:33:36 +09:00
nodchip aa2dc962f5 Added use_draw_in_training_data_generation option to write out draw games to the training data. 2020-06-27 14:00:12 +09:00
rqs 4c926b8eb4 add pgn_to_plain 2020-06-27 13:17:54 +09:00
rqs 0761d9504e add convert_bin and option for draw positions 2020-06-27 13:17:54 +09:00
joergoster 2af46deede Fix include. 2020-06-26 09:12:24 +09:00
joergoster a5fb69008c Bugfix. No legal move is either mate or stalemate. 2020-06-26 09:12:24 +09:00
nodchip 1c8a931309 Merge pull request #18 from joergoster/sf-nnue-nodchip
Update to SF master
2020-06-25 23:09:45 +09:00
joergoster 151a0dda91 Merge branch 'master' into sf-nnue-nodchip 2020-06-25 15:10:12 +02:00
FireFather 8c8a30233c Update evaluate_nnue.cpp 2020-06-25 12:41:32 +09:00
FireFather 86e3fedf7e Update evaluate_nnue.cpp 2020-06-25 04:38:39 +02:00
FireFather 7a3c3eacdf added header guards
5 include files in \eval\nnue\architectures
2020-06-25 10:39:29 +09:00
nodchip ff31d92b94 Merge pull request #14 from joergoster/sf-nnue-nodchip
Update to SF master
2020-06-25 10:38:16 +09:00
FireFather 08d8adbade added header guards
5 include files in \eval\nnue\architectures
2020-06-24 22:41:00 +02:00
joergoster 5e119f5139 Finally. 2020-06-24 20:22:56 +02:00
joergoster 0e932757e5 Re-enable increment operator for Piece.
No functional change.
2020-06-24 20:18:32 +02:00
joergoster 8ef6c837b7 Fix.
Bench: 4471740
2020-06-24 18:04:28 +02:00
joergoster 89bbe86800 Merge branch 'master' of https://github.com/nodchip/Stockfish into sf-nnue-nodchip 2020-06-24 17:47:55 +02:00
nodchip 7818d23afb Merge pull request #13 from zz4032/linux-identifier
Linux identifier corrected.
2020-06-24 09:35:44 +09:00
nodchip 0abd692543 Fixed a build error on Visual Studio. 2020-06-24 09:33:46 +09:00
zz4032 5ae64e2244 Adding mm_malloc.h
Otherwise compiling with 'modern' or 'avx2' architecture on Linux aborts with errors.
2020-06-24 09:28:45 +09:00
zz4032 5aa801e721 Update misc.h 2020-06-23 20:54:50 +02:00
zz4032 3102896a00 Linux identifier corrected. 2020-06-23 20:53:32 +02:00
zz4032 ccd2e602a0 Adding mm_malloc.h
Otherwise compiling with 'modern' or 'avx2' architecture on Linux aborts with errors.
2020-06-23 19:55:54 +02:00
nodchip 999f5ec446 COMP=msys2を指定できるようにした 2020-06-24 00:47:34 +09:00
nodchip 43e78187d7 ARCH=x86-64-avx2を指定できるようにした 2020-06-24 00:27:45 +09:00
nodchip 76b0de40a1 コンパイラーオプションを-std=c++14から-std=c++17に変更した 2020-06-23 23:47:59 +09:00
nodchip c7884470fb Merge branch 'master' of github.com:nodchip/Stockfish 2020-06-22 10:28:02 +09:00
nodchip 6c7a594362 Added "-Wl,-s" option. 2020-06-22 10:27:52 +09:00
tttak 2d5c50d85b eval_nnueコマンド追加 2020-06-22 09:50:22 +09:00
nodchip 33772a0418 コンパイルエラーを修正した 2020-06-08 23:46:06 +09:00
nodchip 5c936572e9 Merge branch 'master' of github.com:official-stockfish/Stockfish
# Conflicts:
#	src/Makefile
#	src/position.cpp
#	src/position.h
#	src/search.cpp
#	src/types.h
#	src/uci.cpp
2020-06-08 23:09:51 +09:00
tttak 91a7557ab4 test nnue test_features等の有効化 2020-06-08 15:07:48 +09:00
nodchip d23f96d156 No pruning at low plies.
This makes those very early depths a bit more reliable.

Thanks, joergoster!

https://github.com/joergoster/Stockfish-NNUE/commit/be7f37187b85b8093ae0741909cbfd7b2bc76871
2020-06-06 18:50:15 +09:00
nodchip 2523f72ff9 盤上から取り除かれた駒に関する差分計算を省き、高速化した 2020-06-03 23:32:08 +09:00
nodchip a85e3055f4 いくつかのターゲットでOpenMPを有効にした 2020-06-02 00:13:35 +09:00
tttak 78134b7641 OpenMPの有効化 2020-06-01 23:51:05 +09:00
nodchip 6703ec8ab0 nnue-gen-sfen-from-original-evalターゲットを追加した 2020-05-30 09:50:29 +09:00
nodchip dd9818c2c1 Added "-static" compiler option. 2020-05-29 09:36:24 +09:00
nodchip f18acf97ed Added the "nnue" target.
Fixed build errors on the "nnue-learn-use-blas" target.
2020-05-28 10:08:51 +09:00
Hisayori Noda e2165155d1 Enabled halfkp_256x2-32-32.h. 2019-07-11 22:47:55 +09:00
Hisayori Noda 1d5f79db1c Merge branch 'master' of github.com:nodchip/Stockfish into k-p_256x2-32-32 2019-07-10 08:18:57 +09:00
tttak c4d30f3649 set_from_packed_sfen()でmirrorをepSquareにも適用するように修正 2019-07-10 08:16:38 +09:00
Hisayori Noda 2d70487caa Enabled k-p_256x2-32-32. 2019-07-08 19:02:09 +09:00
tttak 0be41dbb67 nullmoveとpromotionの場合に評価値の差分計算と全計算の結果が異なっていたのを修正 2019-07-08 18:51:08 +09:00
HiraokaTakuya c643ee0b45 Fix a compile error. 2019-07-08 18:49:47 +09:00
HiraokaTakuya 10aa774d08 Fix a compile error. 2019-07-08 18:49:47 +09:00
HiraokaTakuya 8718438943 std::conditional_t can be used from C++14. 2019-07-08 18:49:47 +09:00
HiraokaTakuya fc5f64b383 Add targets nnue-learn, nnue-learn-use-blas 2019-07-08 18:49:47 +09:00
tttak 3dcd2bb69b Makefileのobjclean:に「./eval/nnue/*.o」等を追加 2019-07-08 18:46:24 +09:00
Hisayori Noda b300a9d43e Enabled halfkp_256x2-32-32. 2019-07-07 21:44:02 +09:00
Hisayori Noda 747d98bf1b Added halfkp-cr-ep_256x2-32-32 architecture. 2019-07-07 20:31:54 +09:00
Hisayori Noda df827ea7ee Added enpassant feature. Added k-p-cr-ep_256x2-32-32 architecture. 2019-07-07 19:24:46 +09:00
Hisayori Noda 92052bc16b Fixed build errors. 2019-07-07 17:22:07 +09:00
Hisayori Noda 5c0037de7f Added the castling right feature. Added k-p-cr_256x2-32-32 architecture. 2019-07-07 17:02:34 +09:00
Hisayori Noda 09e529edd3 Added a hack to avoid crash with binaries compiled by g++ on MSYS2. 2019-07-05 00:22:41 +09:00
Hisayori Noda 89e846c476 Fixed a bug that Learner::qsearch() recognizes stalemate as checkmated. 2019-07-04 23:44:58 +09:00
Hisayori Noda 81262320c3 Revert "Changed the constant value to calculate the winning percentage."
This reverts commit 00f84ed99a.
2019-06-30 11:29:43 +09:00
Hisayori Noda 00f84ed99a Changed the constant value to calculate the winning percentage. 2019-06-26 08:48:48 +09:00
Hisayori Noda 26271586cb Added #if to fix compile errors. 2019-06-24 23:18:17 +09:00
Hisayori Noda a413bf7aad Added hack to avoid crash during machine learning. 2019-06-24 23:17:46 +09:00
Hisayori Noda 9a73df7379 Added test commands. 2019-06-22 00:40:46 +09:00
Hisayori Noda 5772509e8b Disabled TT when EVAL_LEARN is enabled. 2019-06-22 00:40:25 +09:00
Hisayori Noda 641724e3a5 Added debug code. 2019-06-22 00:39:21 +09:00
Hisayori Noda 57ead90f18 Fixed a bug that the game play is invalid. 2019-06-22 00:38:24 +09:00
Hisayori Noda 07dc336b0f Added validation logic. 2019-06-22 00:37:59 +09:00
Hisayori Noda 84a96a3d9c Fixed a compilation error. 2019-06-22 00:37:31 +09:00
Hisayori Noda 998d8721bd Fixed a bug that White and Black are reversed. 2019-06-22 00:37:10 +09:00
Hisayori Noda 9dab4660ce Added source files. 2019-06-22 00:36:42 +09:00
Hisayori Noda 90ef97dcbd Fixed crash bugs. 2019-06-20 00:25:40 +09:00
Hisayori Noda 24576d77ab Merged uci parse logic. 2019-06-18 21:19:51 +09:00
Hisayori Noda f58d616198 Fixed compile errors when EVAL_LEARN or EVAL_NNUE are defined. 2019-06-18 20:28:50 +09:00
Hisayori Noda bcd6985871 Merged the training data generator and the machine learning logic from YaneuraOu. 2019-06-18 08:48:05 +09:00
Hisayori Noda 87445881ec Added #ifdef statements to switch the legacy evaluation function and NNUE evaluation function. 2019-06-16 11:11:16 +09:00
Hisayori Noda 48bfe86d27 Implemented the logic to update Eval List and Dirty Pieces. 2019-06-16 10:33:53 +09:00
Hisayori Noda b330602cdc Fixed compile errors. 2019-06-15 17:08:47 +09:00
Hisayori Noda 9964fbbe25 Reverted evaluate.cpp. 2019-06-15 11:46:54 +09:00
Hisayori Noda db02ddcc90 Added files for NNUE. 2019-06-09 10:40:12 +09:00
59 changed files with 17219 additions and 168 deletions
+3 -76
View File
@@ -17,7 +17,6 @@ jobs:
env:
COMPILER: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
CXXFLAGS: "-Werror"
strategy:
matrix:
config:
@@ -25,15 +24,12 @@ jobs:
# run_expensive_tests: true
# run_32bit_tests: true
# run_64bit_tests: true
# run_armv8_tests: true
# run_armv7_tests: true
- {
name: "Ubuntu 20.04 GCC",
os: ubuntu-20.04,
compiler: g++,
comp: gcc,
run_expensive_tests: true,
run_32bit_tests: true,
run_64bit_tests: true,
shell: 'bash {0}'
}
@@ -42,26 +38,9 @@ jobs:
os: ubuntu-20.04,
compiler: clang++,
comp: clang,
run_32bit_tests: true,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "Ubuntu 20.04 NDK armv8",
os: ubuntu-20.04,
compiler: aarch64-linux-android21-clang++,
comp: ndk,
run_armv8_tests: true,
shell: 'bash {0}'
}
- {
name: "Ubuntu 20.04 NDK armv7",
os: ubuntu-20.04,
compiler: armv7a-linux-androideabi21-clang++,
comp: ndk,
run_armv7_tests: true,
shell: 'bash {0}'
}
- {
name: "MacOS 10.15 Apple Clang",
os: macos-10.15,
@@ -88,26 +67,6 @@ jobs:
msys_env: 'x86_64-gcc',
shell: 'msys2 {0}'
}
- {
name: "Windows 2022 Mingw-w64 GCC i686",
os: windows-2022,
compiler: g++,
comp: mingw,
run_32bit_tests: true,
msys_sys: 'mingw32',
msys_env: 'i686-gcc',
shell: 'msys2 {0}'
}
- {
name: "Windows 2022 Mingw-w64 Clang x86_64",
os: windows-2022,
compiler: clang++,
comp: clang,
run_64bit_tests: true,
msys_sys: 'clang64',
msys_env: 'clang-x86_64-clang',
shell: 'msys2 {0}'
}
defaults:
run:
@@ -122,7 +81,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install expect valgrind g++-multilib qemu-user
sudo apt install expect valgrind g++-multilib
- name: Setup msys and install required packages
if: runner.os == 'Windows'
@@ -142,7 +101,6 @@ jobs:
- name: Check compiler
run: |
export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin
$COMPILER -v
- name: Test help target
@@ -154,7 +112,7 @@ jobs:
- name: Test debug x86-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
export CXXFLAGS="-D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-32 optimize=no debug=yes build
../tests/signature.sh $benchref
@@ -192,7 +150,7 @@ jobs:
- name: Test debug x86-64-modern build
if: ${{ matrix.config.run_64bit_tests }}
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
export CXXFLAGS="-D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-64-modern optimize=no debug=yes build
../tests/signature.sh $benchref
@@ -264,37 +222,6 @@ jobs:
make clean
make -j2 ARCH=x86-64-vnni256 build
# armv8 tests
- name: Test armv8 build
if: ${{ matrix.config.run_armv8_tests }}
run: |
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j2 ARCH=armv8 build
../tests/signature.sh $benchref
# armv7 tests
- name: Test armv7 build
if: ${{ matrix.config.run_armv7_tests }}
run: |
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j2 ARCH=armv7 build
../tests/signature.sh $benchref
- name: Test armv7-neon build
if: ${{ matrix.config.run_armv7_tests }}
run: |
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j2 ARCH=armv7-neon build
../tests/signature.sh $benchref
# Other tests
- name: Check perft and search reproducibility
+35
View File
@@ -166,6 +166,41 @@ For developers the following non-standard commands might be of interest, mainly
* #### flip
Flips the side to move.
### Generating Training Data
To generate training data from the classic eval, use the generate_training_data command with the setting "Use NNUE" set to "false". The given example is generation in its simplest form. There are more commands.
```
uci
setoption name PruneAtShallowDepth value false
setoption name Use NNUE value false
setoption name Threads value X
setoption name Hash value Y
setoption name SyzygyPath value path
isready
generate_training_data depth A count B keep_draws 1 eval_limit 32000
```
- `A` is the searched depth per move, or how far the engine looks forward. This value is an integer.
- `B` is the amount of positions generated. This value is also an integer.
Specify how many threads and how much memory you would like to use with the `x` and `y` values. The option SyzygyPath is not necessary, but if you would like to use it, you must first have Syzygy endgame tablebases on your computer, which you can find [here](http://oics.olympuschess.com/tracker/index.php). You will need to have a torrent client to download these tablebases, as that is probably the fastest way to obtain them. The `path` is the path to the folder containing those tablebases. It does not have to be surrounded in quotes.
This will create a file named "training_data.binpack" in the same folder as the binary containing the generated training data. Once generation is done, you can rename the file to something like "1billiondepth12.binpack" to remember the depth and quantity of the positions and move it to a folder named "trainingdata" in the same directory as the binaries.
You will also need validation data that is used for loss calculation and accuracy computation. Validation data is generated in the same way as training data, but generally at most 1 million positions should be used as there's no need for more and it would just slow the learning process down. It may also be better to slightly increase the depth for validation data. After generation you can rename the validation data file to "val.binpack" and drop it in a folder named "validationdata" in the same directory to make it easier.
## Training data formats.
Currently there are 3 training data formats. Two of them are supported directly.
- `.bin` - the original training data format. Uses 40 bytes per entry. Is supported directly by the `generate_training_data` command.
- `.plain` - a human readable training data format. This one is not supported directly by the `generate_training_data` command. It should not be used for data exchange because it's less compact than other formats. It is mostly useful for inspection of the data.
- `.binpack` - a compact binary training data format that exploits positions chains to further reduce size. It uses on average between 2 to 3 bytes per entry when generating data with `generate_training_data`. It is supported directly by `generate_training_data` command. It is currently the default for the `generate_training_data` command. A more in depth description can be found [here](docs/binpack.md)
### Conversion between formats.
There is a builting converted that support all 3 formats described above. Any of them can be converted to any other. For more information and usage guide see [here](docs/convert.md).
## A note on classical evaluation versus NNUE evaluation
+42
View File
@@ -0,0 +1,42 @@
# Binpack
Binpack is a binary training data storage format designed to take advantage of position chains differing by a single move. Therefore it is very good at compactly storing data generated from real games (as opposed to random positions for example sourced from an opening book).
It is currently implemented through a single header library in `extra/nnue_data_binpack_format.h`.
Below follows a rough description of the format in a BNF-like notation.
```
[[nodiscard]] std::uint16_t signedToUnsigned(std::int16_t a) {
std::uint16_t r;
std::memcpy(&r, &a, sizeof(std::uint16_t));
if (r & 0x8000) r ^= 0x7FFF; // flip value bits if negative
r = (r << 1) | (r >> 15); // store sign bit at bit 0
return r;
}
file := <block>*
block := BINP<chain>*
chain := <stem><movetext>
stem := <pos><move><score><ply_and_result><rule50> (32 bytes)
pos := https://github.com/Sopel97/nnue_data_compress/blob/master/src/chess/Position.h#L1166 (24 bytes)
move := https://github.com/Sopel97/nnue_data_compress/blob/master/src/chess/Chess.h#L1044 (2 bytes)
score := signedToUnsigned(score) (2 bytes, big endian)
ply_and_result := ply bitwise_or (signedToUnsigned(result) << 14) (2 bytes, big endian)
rule50 := rule_50_counter (2 bytes, big endian)
// this is a small defect from old version,
I didn't want to break backwards compatibility. Effectively means that there's
one byte left for something else in the future because rule50 always fits in one byte.
movetext := <count><move_and_score>*
count := number of plies in the movetext (2 bytes, big endian). Can be 0.
move_and_score := <encoded_move><encoded_score> (~2 bytes)
encoded_move := oof this one is complicated to explain.
https://github.com/Sopel97/nnue_data_compress/blob/master/src/compress_file.cpp#L827.
https://github.com/Sopel97/chess_pos_db/blob/master/docs/bcgn/variable_length.md
encoded_score := https://en.wikipedia.org/wiki/Variable-width_encoding
with block size of 4 bits + 1 bit for extension bit.
Encoded value is signedToUnsigned(-prev_score - current_score)
(scores are always seen from the perspective of side to move in <pos>, that's why the '-' before prev_score)
```
+18
View File
@@ -0,0 +1,18 @@
# Convert
`convert` allows conversion of training data between any of `.plain`, `.bin`, and `.binpack`.
As all commands in stockfish `convert` can be invoked either from command line (as `stockfish.exe convert ...`) or in the interactive prompt.
The syntax of this command is as follows:
```
convert from_path to_path [append] [validate]
```
`from_path` is the path to the file to convert from. The type of the data is deduced based on its extension (one of `.plain`, `.bin`, `.binpack`).
`to_path` is the path to an output file. The type of the data is deduced from its extension. If the file does not exist it is created.
`append` and `validate` can come in any order and are optional.
If `append` not specified then the output file will be truncated prior to any writes. If `append` is specified then the converted training data will be appended to the end of the output file.
If `validate` is specified then the conversion will stop on the first illegal move found and a diagnostic will be shown.
+67
View File
@@ -0,0 +1,67 @@
# generate_training_data
`generate_training_data` command allows generation of training data from self-play in a manner that suits training better than traditional games. It introduces random moves to diversify openings, and fixed depth evaluation.
As all commands in stockfish `generate_training_data` can be invoked either from command line (as `stockfish.exe generate_training_data ...`, but this is not recommended because it's not possible to specify UCI options before `generate_training_data` executes) or in the interactive prompt.
It is recommended to set the `PruneAtShallowDepth` UCI option to `false` as it will increase the quality of fixed depth searches.
It is recommended to keep the `EnableTranspositionTable` UCI option at the default `true` value as it will make the generation process faster without noticably harming the uniformity of the data.
`generate_training_data` takes named parameters in the form of `generate_training_data param_1_name param_1_value param_2_name param_2_value ...`.
Currently the following options are available:
`set_recommended_uci_options` - this is a modifier not a parameter, no value follows it. If specified then some UCI options are set to recommended values.
`depth` - sets minimum and maximum depth of evaluation of each position. Default: 3.
`mindepth` - minimum depth of evaluation of each position. If not specified then the same as `depth`.
`maxdepth` - minimum depth of evaluation of each position. If not specified then the same as `depth`.
`nodes` - the number of nodes to use for evaluation of each position. This number is multiplied by the number of PVs of the current search. This does NOT override the `depth` and `depth2` options. If specified then whichever of depth or nodes limit is reached first applies.
`count` - the number of training data entries to generate. 1 entry == 1 position. If both `count` and `max_time_*` are specified the data generation process will end when any of conditions is fullfilled. Default: 8000000000 (8B).
`max_time_seconds`, `max_time_minutes`, `max_time_hours` - specifies the maximum runtime for the data generation. The data generation will NOT be interrupted while a self-play game is in progress. If both `count` and `max_time_*` are specified the data generation process will end when any of conditions is fullfilled. Default: \~250 years.
`output_file_name` - the name of the file to output to. If the extension is not present or doesn't match the selected training data format the right extension will be appened. Default: generated_kifu
`eval_limit` - evaluations with higher absolute value than this will not be written and will terminate a self-play game. Should not exceed 10000 which is VALUE_KNOWN_WIN, but is only hardcapped at mate in 2 (\~30000). Default: 3000
`random_move_min_ply` - the minimal ply at which a random move may be executed instead of a move chosen by search. Default: 1.
`random_move_max_ply` - the maximal ply at which a random move may be executed instead of a move chosen by search. Default: 24.
`random_move_count` - maximum number of random moves in a single self-play game. Default: 5.
`random_move_like_apery` - either 0 or 1. If 1 then random king moves will be followed by a random king move from the opponent whenever possible with 50% probability. Default: 0.
`random_multi_pv` - the number of PVs used for determining the random move. If not specified then a truly random move will be chosen. If specified then a multiPV search will be performed the random move will be one of the moves chosen by the search.
`random_multi_pv_diff` - Makes the multiPV random move selection consider only moves that are at most `random_multi_pv_diff` worse than the next best move. Default: 30000 (all multiPV moves).
`random_multi_pv_depth` - the depth to use for multiPV search for random move. Default: `depth2`.
`random_multi_pv_nodes` - the maximum number of nodes for a multiPV search for random move. Default: `nodes`.
`write_min_ply` - minimum ply for which the training data entry will be emitted. Default: 16.
`write_max_ply` - maximum ply for which the training data entry will be emitted. Default: 400.
`book` - a path to an opening book to use for the starting positions. Currently only .epd format is supported. If not specified then the starting position is always the standard chess starting position.
`save_every` - the number of training data entries per file. If not specified then there will be always one file. If specified there may be more than one file generated (each having at most `save_every` training data entries) and each file will have a unique number attached.
`random_file_name` - if specified then the output filename will be chosen randomly. Overrides `output_file_name`.
`keep_draws` - either 0 or 1. If 1 then training data from drawn games will be emitted too. Default: 1.
`adjudicate_draws_by_score` - either 0 or 1. If 1 then drawn games will be adjudicated when the score remains 0 for at least 8 plies after ply 80. Default: 1.
`adjudicate_draws_by_insufficient_mating_material` - either 0 or 1. If 1 then position with insufficient material will be adjudicated as draws. Default: 1.
`data_format` - format of the training data to use. Either `bin` or `binpack`. Default: `binpack`.
`seed` - seed for the PRNG. Can be either a number or a string. If it's a string then its hash will be used. If not specified then the current time will be used.
+41
View File
@@ -0,0 +1,41 @@
# generate_training_data_nonpv
`generate_training_data_nonpv` command allows generation of training data from self-play in a manner that suits training better than traditional games. It plays fixed nodes self play games for exploration and records [some of] the evaluated positions. Then rescores them with fixed depth search.
As all commands in stockfish `generate_training_data_nonpv` can be invoked either from command line (as `stockfish.exe generate_training_data_nonpv ...`, but this is not recommended because it's not possible to specify UCI options before `generate_training_data_nonpv` executes) or in the interactive prompt.
It is recommended to set the `PruneAtShallowDepth` UCI option to `false` as it will increase the quality of fixed depth searches.
It is recommended to keep the `EnableTranspositionTable` UCI option at the default `true` value as it will make the generation process faster without noticably harming the uniformity of the data.
`generate_training_data_nonpv` takes named parameters in the form of `generate_training_data_nonpv param_1_name param_1_value param_2_name param_2_value ...`.
Currently the following options are available:
`depth` - the search depth to use for rescoring. Default: 3.
`count` - the number of training data entries to generate. 1 entry == 1 position. Default: 1000000 (1M).
`exploration_min_nodes` - the min number of nodes to use for exploraton during selfplay. Default: 5000.
`exploration_max_nodes` - the max number of nodes to use for exploraton during selfplay. The number of nodes is chosen from a uniform distribution between min and max. Default: 15000.
`exploration_save_rate` - the ratio of positions seen during exploration self play games that are saved for later rescoring. Default: 0.01 (meaning 1 in 100 positions seen during search get saved for rescoring).
`output_file` - the name of the file to output to. If the extension is not present or doesn't match the selected training data format the right extension will be appened. Default: generated_gensfen_nonpv
`eval_limit` - evaluations with higher absolute value than this will not be written and will terminate a self-play game. Should not exceed 10000 which is VALUE_KNOWN_WIN, but is only hardcapped at mate in 2 (\~30000). Default: 4000
`exploration_eval_limit` - same as `eval_limit` but used during exploration with a value from fixed depth search.
`exploration_min_pieces` - the min number of pieces in the self play games to start the fixed depth search. Note that even if there's N pieces on the board the fixed nodes search usually reaches positions with less pieces and they are saved too. Default: 8.
`exploration_max_ply` the max ply for the exploration self play. Default: 200.
`smart_fen_skipping` - this is a flag option. When specified some position that are not good candidates for teaching are removed from the output. This includes positions where the best move is a capture or promotion, and position where a king is in check.
`book` - a path to an opening book to use for the starting positions. Currently only .epd format is supported. If not specified then the starting position is always the standard chess starting position.
`data_format` - format of the training data to use. Either `bin` or `binpack`. Default: `binpack`.
`seed` - seed for the PRNG. Can be either a number or a string. If it's a string then its hash will be used. If not specified then the current time will be used.
+41
View File
@@ -0,0 +1,41 @@
# Stats
`gather_statistics` command allows gathering various statistics from a .bin or a .binpack file. The syntax is `gather_statistics (GROUP)* input_file FILENAME`. There can be many groups specified. Any statistic gatherer that belongs to at least one of the specified groups will be used.
Simplest usage: `stockfish.exe gather_statistics all input_file a.binpack`
Any name that doesn't designate an argument name or is not an argument will be interpreted as a group name.
## Parameters
`input_file` - the path to the .bin or .binpack input file to read
`output_file` - optional path to the output file to write the results too. Results are always written on the console, so if this is specified the results will be written in both places.
`max_count` - the maximum number of positions to process. Default: no limit.
## Groups
`all` - a special group designating all statistics gatherers available.
`position_count` - the total number of positions in the file.
`king`, `king_square_count` - the number of times a king was on each square. Output is layed out as a chessboard, with the 8th rank being the topmost. Separate values for white and black kings.
`move`, `move_from_count` - same as `king_square_count` but for from_sq(move)
`move`, `move_to_count` - same as `king_square_count` but for to_sq(move)
`move`, `move_type` - the number of moves with each type. Includes normal, captures, castling, promotions, enpassant. The groups are not disjoint.
`move`, `moved_piece_type` - the number of times a piece of each type was moved
`piece_count` - the histogram of the number of pieces on the board
`ply_discontinuities` - the number of times the ply jumped by a value different than 1 between two consecutive positions. Usually the number of games.
`material_imbalance` - the histogram of imbalances, with values computed using "simple eval", that is pawn=1, bishop=knight=3, rook=5, queen=9
`results` - the distribution of game results
`endgames_6man` - distribution of endgame configurations for <=6 pieces (including kings)
+42
View File
@@ -0,0 +1,42 @@
# Transform
`transform` command exposes subcommands that perform some specific transformation over data. The call syntax is `transform <subcommand>`. Currently implemented subcommands are listed and described below.
## `nudged_static`
`transform nudged_static` takes named parameters in the form of `transform nudged_static param_1_name param_1_value param_2_name param_2_value ...` and flag parameters which don't require values.
This command goes through positions in the input files and replaces the scores with new ones - generated from static eval - but slightly adjusted based on the scores in the original input file.
Currently the following options are available:
`input_file` - path to the input file. Supports bin and binpack formats. Default: in.binpack.
`output_file` - path to the output file. Supports bin and binpack formats. Default: out.binpack.
`absolute` - states that the adjustment should be bounded by an absolute value. After this token follows the maximum absolute adjustment. Values are always adjusted towards scores in the input file. This is the default mode. Default maximum adjustement: 5.
`relative` - states that the adjustment should be bounded by a value relative in magnitude to the static eval value. After this token follows the maximum relative change - a floating point value greater than 0. For example a value of 0.1 only allows changing the static eval by at most 10% towards the score from the input file.
`interpolate` states that the output score should be a value interpolated between static eval and the score from the input file. After this token follows the interpolation constant `t`. `t` of 0 means that only static eval is used. `t` of 1 means that only score from the input file is used. `t` of 0.5 means that the static eval and input score are averaged. It accepts values outside of range `<0, 1>`, but the usefulness is questionable.
## `rescore`
`transform rescore` takes named parameters in the form of `transform rescore param_1_name param_1_value param_2_name param_2_value ...` and flag parameters which don't require values.
This tool respects the UCI option `Threads` and uses all available threads.
This command takes a path to the input file that is either a .epd file which contains one FEN per line or a .bin or .binpack file and outputs a .bin or .binpack file with these positions rescored with specified depth search.
Currently the following options are available:
`input_file` - path to the input file. Default: in.binpack.
`output_file` - path to the output .bin or .binpack file. The file is opened in append mode. Default: out.binpack.
`depth` - the search depth to use for rescoring. Default: 3.
`keep_moves` - whether to keep moves from the input file if available. Allows to keep compression in .binpack. Default: 1.
`research_count` - number of additional searches of depth N done on the same position before using the eval. Default: 0.
+12
View File
@@ -0,0 +1,12 @@
# validate_training_data
`validate_training_data` allows validation of training data of types `.plain`, `.bin`, and `.binpack`.
As all commands in stockfish `validate_training_data` can be invoked either from command line (as `stockfish.exe validate_training_data ...`) or in the interactive prompt.
The syntax of this command is as follows:
```
validate_training_data in_path
```
`in_path` is the path to the file to validate. The type of the data is deduced based on its extension (one of `.plain`, `.bin`, `.binpack`).
+52
View File
@@ -0,0 +1,52 @@
# `pgn_to_plain`
This script converts pgn files into text file to apply `learn convert_bin` command. You need to import [python-chess](https://pypi.org/project/python-chess/) to use this script.
pip install python-chess
# Example of Qhapaq's finetune using `pgn_to_plain`
## Download data
You can download data from [here](http://rebel13.nl/index.html)
## Convert pgn files
**Important : convert text will be superheavy (approx 200 byte / position)**
python pgn_to_plain.py --pgn "pgn/*.pgn" --start_ply 1 --output converted_pgn.txt
`--pgn` option supports wildcard. When you use pgn files with elo >= 3300, You will get 1.7 GB text file.
## Convert into training data
### Example build command
make nnue-learn ARCH=x86-64
See `src/Makefile` for detail.
### Convert
./stockfish
learn convert_bin converted_pgn.txt output_file_name pgn_bin.bin
learn shuffle pgn_bin.bin
You also need to prepare validation data for training like following.
python pgn_to_plain.py --pgn "pgn/ccrl-40-15-3400.pgn" --start_ply 1 --output ccrl-40-15-3400.txt
./stockfish
learn convert_bin ccrl-40-15-3400.txt ccrl-40-15-3400_plain.bin
### Learn
./stockfish
setoption name Threads value 8
learn shuffled_sfen.bin newbob_decay 0.5 validation_set_file_name ccrl-40-15-3400_plain.bin nn_batch_size 50000 batchsize 1000000 eval_save_interval 8000000 eta 0.05 lambda 0.0 eval_limit 3000 mirror_percentage 0 use_draw_in_training 1
+42
View File
@@ -0,0 +1,42 @@
import sys
ENTRY_SIZE = 40
NUM_ENTRIES_IN_CHUNK = 1024*1024
def copy(infile, outfile, count, times):
if times > 1:
outfile.write(infile.read(count*ENTRY_SIZE)*times)
else:
offset = 0
while offset < count:
to_read = NUM_ENTRIES_IN_CHUNK if offset + NUM_ENTRIES_IN_CHUNK <= count else count - offset
outfile.write(infile.read(to_read*ENTRY_SIZE))
offset += NUM_ENTRIES_IN_CHUNK
def work():
filename = sys.argv[1]
offset = int(sys.argv[2])
count = int(sys.argv[3])
times = int(sys.argv[4]) if len(sys.argv) >= 5 else 1
with open(filename, 'rb') as infile:
infile.seek(offset * ENTRY_SIZE)
filename_parts = filename.split('.')
out_path = '.'.join(filename_parts[:-1]) + '_' + str(offset) + '_' + str(count) + '_' + str(times) + '.' + filename_parts[-1]
with open(out_path, 'wb') as outfile:
copy(infile, outfile, count, times)
def show_help():
print('Usage: python extract_bin.py filename offset count [times]')
print('filename - the path to the .bin file to process')
print('offset - the number of sfens to skip')
print('count - the number of sfens to extract')
print('times - the number of times to repeat the extracted sfens. Default = 1')
print('The result is saved in a new file named `filename.stem`_`offset`_`count`_`times`.bin')
if len(sys.argv) < 4:
show_help()
else:
work()
+86
View File
@@ -0,0 +1,86 @@
import struct
import sys
import os
import random
from pathlib import Path
def copy_next_chunk(in_file, out_file):
chunk_header = in_file.read(8)
assert chunk_header[0:4] == b"BINP"
size = struct.unpack("<I", chunk_header[4:])[0]
out_file.write(chunk_header)
data = in_file.read(size)
out_file.write(data)
return size + 8
def main():
if len(sys.argv) < 4:
print("Usage: python interleave_binpacks.py infile1 ... infileN outfile")
print(" The output binpack, will contain all data from the input files.")
print(" Data is read sequentially from the input, randomly alternating between files.")
return
# open last arg as output file name
out_filename = sys.argv[-1]
print("outfile: ", out_filename)
if Path(out_filename).exists():
print(
"Output path {} already exists. Please specify a path to a file that does not exist.".format(
out_filename
)
)
return
out_file = open(out_filename, "wb")
# open other args as input file names, and get their sizes
in_filenames = []
for i in range(1, len(sys.argv) - 1):
in_filenames.append(sys.argv[i])
print("infiles: ", in_filenames)
in_files = []
in_files_remaining = []
for in_filename in in_filenames:
in_file = open(in_filename, "rb")
in_files.append(in_file)
file_size = os.path.getsize(in_filename)
in_files_remaining.append(file_size)
# randomly pick a file, with a probability related to their sizes.
# copy from the front and keep track of remaining sizes
total_remaining = sum(in_files_remaining)
print("Merging {} bytes ".format(total_remaining))
total_size = 0
report_every = 100
prev_mib = -report_every
while total_remaining > 0:
where = random.randrange(total_remaining)
i = 0
while where >= in_files_remaining[i]:
where -= in_files_remaining[i]
i += 1
size = copy_next_chunk(in_files[i], out_file)
in_files_remaining[i] -= size
total_remaining -= size
total_size += size
mib = total_size // 1024 // 1024
if mib // 100 != prev_mib // 100:
print("Copied {} MiB".format(mib))
prev_mib = mib
out_file.close()
for in_file in in_files:
in_file.close()
print("Merged {} bytes".format(total_size))
main()
+110
View File
@@ -0,0 +1,110 @@
import chess.pgn
import argparse
import glob
import re
from typing import List
# todo close in c++ tools using pgn-extract
# https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/help.html#-w
commentRe = re.compile("([+-]*M*[0-9.]*)/([0-9]*)")
mateRe = re.compile("([+-])M([0-9]*)")
flip_black = False
def parse_result(result_str:str, board:chess.Board) -> int:
if result_str == "1/2-1/2":
return 0
if result_str == "0-1":
if board.turn == chess.WHITE:
return -1
else:
return 1
elif result_str == "1-0":
if board.turn == chess.WHITE:
return 1
else:
return -1
else:
print("illegal result", result_str)
raise ValueError
def game_sanity_check(game: chess.pgn.Game) -> bool:
if not game.headers["Result"] in ["1/2-1/2", "0-1", "1-0"]:
print("invalid result", game.headers["Result"])
return False
return True
def parse_comment_for_score(comment_str: str, board: chess.Board) -> int:
global commentRe
global mateRe
global flip_black
try:
m = commentRe.search(comment_str)
if m:
score = m.group(1)
# depth = int(m.group(2))
m = mateRe.search(score)
if m:
if m.group(1) == "+":
score = 32000 - int(m.group(2))
else:
score = -32000 + int(m.group(2))
else:
score = int(float(score) * 208) # pawn to SF PawnValueEg
if flip_black and board.turn == chess.BLACK:
score = -score
else:
score = 0
except:
score = 0
return score
def parse_game(game: chess.pgn.Game, writer, start_play: int=1)->None:
board: chess.Board = game.board()
if not game_sanity_check(game):
return
result: str = game.headers["Result"]
ply = 0
for node in game.mainline():
move = node.move
if ply >= start_play:
comment: str = node.comment
writer.write("fen " + board.fen() + "\n")
writer.write("move " + str(move) + "\n")
writer.write("score " + str(parse_comment_for_score(comment, board)) + "\n")
writer.write("ply " + str(ply)+"\n")
writer.write("result " + str(parse_result(result, board)) +"\n")
writer.write("e\n")
ply += 1
board.push(move)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--pgn", type=str, required=True)
parser.add_argument("--start_ply", type=int, default=1)
parser.add_argument("--output", type=str, default="plain.txt")
parser.add_argument("--flip_black_score", action='store_true', dest='flip_black_score', help="Flip black score. Default: False")
args = parser.parse_args()
global flip_black
flip_black = args.flip_black_score
pgn_files: List[str] = glob.glob(args.pgn)
pgn_files = sorted(pgn_files, key=lambda x:float(re.findall("-(\d+).pgn",x)[0] if re.findall("-(\d+).pgn",x) else 0.0))
f = open(args.output, 'w')
for pgn_file in pgn_files:
print("parse", pgn_file)
pgn_loader = open(pgn_file)
while True:
game = chess.pgn.read_game(pgn_loader)
if game is None:
break
parse_game(game, f, args.start_ply)
f.close()
if __name__=="__main__":
main()
+89
View File
@@ -0,0 +1,89 @@
import struct
import sys
import os
import random
from pathlib import Path
def index_binpack(file):
print('Indexing...')
index = []
offset = 0
report_every = 100
prev_mib = -report_every
while file.peek():
chunk_header = file.read(8)
assert chunk_header[0:4] == b'BINP'
size = struct.unpack('<I', chunk_header[4:])[0]
file.seek(size, os.SEEK_CUR)
index.append((offset, size + 8))
offset += size + 8
mib = offset // 1024 // 1024
if mib // 100 != prev_mib // 100:
print('Indexed {} MiB'.format(mib))
prev_mib = mib
return index
def copy_binpack_indexed(in_file, index, out_files):
print('Copying...')
total_size = 0
report_every = 100
prev_mib = -report_every
nextfile = 0
for offset, size in index:
in_file.seek(offset, os.SEEK_SET)
data = in_file.read(size)
assert len(data) == size
out_files[nextfile].write(data)
nextfile = (nextfile + 1) % len(out_files)
total_size += size
mib = total_size // 1024 // 1024
if mib // 100 != prev_mib // 100:
print('Copied {} MiB'.format(mib))
prev_mib = mib
def main():
if len(sys.argv) < 3:
print('Usage: python shuffle_binpack.py infile outfile [split_count]')
return
in_filename = sys.argv[1]
if len(sys.argv) > 3:
# split the infile in split_count pieces, creating new outfile names based on the provided name
basefile = sys.argv[2]
split_count = int(sys.argv[3])
base=os.path.splitext(basefile)[0]
ext=os.path.splitext(basefile)[1]
out_filenames = []
for i in range(split_count):
out_filenames.append(base+"_{}".format(i)+ext)
else:
out_filenames = [sys.argv[2]]
for out_filename in out_filenames:
if (Path(out_filename).exists()):
print('Output path {} already exists. Please specify a path to a file that does not exist.'.format(out_filename))
return
print(out_filenames)
in_file = open(in_filename, 'rb')
index = index_binpack(in_file)
print('Shuffling...')
random.shuffle(index)
out_files = []
for out_filename in out_filenames:
out_files.append(open(out_filename, 'wb'))
copy_binpack_indexed(in_file, index, out_files)
in_file.close()
for out_file in out_files:
out_file.close()
main()
+32 -13
View File
@@ -44,6 +44,12 @@ else
EXE = stockfish
endif
### Establish the operating system name
KERNEL = $(shell uname -s)
ifeq ($(KERNEL),Linux)
OS = $(shell uname -o)
endif
### Installation dir definitions
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
@@ -51,19 +57,30 @@ BINDIR = $(PREFIX)/bin
### Built-in benchmark for pgo-builds
ifeq ($(SDE_PATH),)
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
PGOGENSFEN = $(WINE_PATH) ./$(EXE) gensfen depth 3 loop 1000 sfen_format bin output_file_name $(PGO_TRAINING_DATA_FILE)
else
PGOBENCH = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) bench
PGOGENSFEN = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) gensfen depth 3 loop 1000 sfen_format bin output_file_name $(PGO_TRAINING_DATA_FILE)
endif
### Source and object files
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp
nnue/evaluate_nnue.cpp \
nnue/features/half_ka_v2_hm.cpp \
tools/validate_training_data.cpp \
tools/sfen_packer.cpp \
tools/training_data_generator.cpp \
tools/training_data_generator_nonpv.cpp \
tools/opening_book.cpp \
tools/convert.cpp \
tools/transform.cpp \
tools/stats.cpp
OBJS = $(notdir $(SRCS:.cpp=.o))
VPATH = syzygy:nnue:nnue/features
VPATH = syzygy:nnue:nnue/features:eval:extra:tools
### ==========================================================================
### Section 2. High-level Configuration
@@ -351,8 +368,9 @@ ifeq ($(MAKELEVEL),0)
export ENV_LDFLAGS := $(LDFLAGS)
endif
CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17
ADDITIONAL_INCLUDE_DIRECTORIES = -I.
CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(ADDITIONAL_INCLUDE_DIRECTORIES) $(EXTRACXXFLAGS)
DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17 $(ADDITIONAL_INCLUDE_DIRECTORIES)
LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS)
ifeq ($(COMP),)
@@ -362,7 +380,7 @@ endif
ifeq ($(COMP),gcc)
comp=gcc
CXX=g++
CXXFLAGS += -pedantic -Wextra -Wshadow
CXXFLAGS += -pedantic -Wextra -Wshadow -lstdc++fs
ifeq ($(arch),$(filter $(arch),armv7 armv8))
ifeq ($(OS),Android)
@@ -441,8 +459,8 @@ ifeq ($(COMP),clang)
endif
ifeq ($(KERNEL),Darwin)
CXXFLAGS += -mmacosx-version-min=10.14
LDFLAGS += -mmacosx-version-min=10.14
CXXFLAGS += -mmacosx-version-min=10.15
LDFLAGS += -mmacosx-version-min=10.15
ifneq ($(arch),any)
CXXFLAGS += -arch $(arch)
LDFLAGS += -arch $(arch)
@@ -518,13 +536,13 @@ ifneq ($(comp),mingw)
# Haiku has pthreads in its libroot, so only link it in on other platforms
ifneq ($(KERNEL),Haiku)
ifneq ($(COMP),ndk)
LDFLAGS += -lpthread
endif
LDFLAGS += -lpthread
endif
endif
endif
endif
### 3.2.1 Debugging
### 3.2.2 Debugging
ifeq ($(debug),no)
CXXFLAGS += -DNDEBUG
else
@@ -661,7 +679,7 @@ ifeq ($(neon),yes)
CXXFLAGS += -mfpu=neon
endif
endif
endif
endif
endif
### 3.7 pext
@@ -803,6 +821,7 @@ profile-build: net config-sanity objclean profileclean
@echo ""
@echo "Step 2/4. Running benchmark for pgo-build ..."
$(PGOBENCH) 2>&1 | tail -n 4
$(PGOGENSFEN) 2>&1 | tail -n 4
@echo ""
@echo "Step 3/4. Building optimized executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
@@ -849,12 +868,12 @@ net:
# clean binaries and objects
objclean:
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
@rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o ./tools/*.o ./extra/*.o ./eval/*.o
# clean auxiliary profiling files
profileclean:
@rm -rf profdir
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s ./tools/*.gcda ./extra/*.gcda ./eval/*.gcda
@rm -f stockfish.profdata *.profraw
@rm -f stockfish.*args*
@rm -f stockfish.*lt*
+39 -13
View File
@@ -27,6 +27,8 @@
#include <streambuf>
#include <vector>
#include "nnue/evaluate_nnue.h"
#include "bitboard.h"
#include "evaluate.h"
#include "material.h"
@@ -37,7 +39,6 @@
#include "uci.h"
#include "incbin/incbin.h"
// Macro to embed the default efficiently updatable neural network (NNUE) file
// data in the engine binary (using incbin.h, by Dale Weiler).
// This macro invocation will declare the following three variables
@@ -53,15 +54,28 @@
const unsigned int gEmbeddedNNUESize = 1;
#endif
using namespace std;
namespace Stockfish {
namespace Eval {
bool useNNUE;
string currentEvalFileName = "None";
namespace NNUE {
string currentEvalFileName = "None";
UseNNUEMode useNNUE;
static UseNNUEMode nnue_mode_from_option(const UCI::Option& mode)
{
if (mode == "false")
return UseNNUEMode::False;
else if (mode == "true")
return UseNNUEMode::True;
else if (mode == "pure")
return UseNNUEMode::Pure;
return UseNNUEMode::False;
}
}
/// NNUE::init() tries to load a NNUE network at startup time, or when the engine
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
@@ -73,8 +87,8 @@ namespace Eval {
void NNUE::init() {
useNNUE = Options["Use NNUE"];
if (!useNNUE)
useNNUE = nnue_mode_from_option(Options["Use NNUE"]);
if (useNNUE == UseNNUEMode::False)
return;
string eval_file = string(Options["EvalFile"]);
@@ -124,7 +138,7 @@ namespace Eval {
if (eval_file.empty())
eval_file = EvalFileDefaultName;
if (useNNUE && currentEvalFileName != eval_file)
if (useNNUE != UseNNUEMode::False && currentEvalFileName != eval_file)
{
string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
@@ -142,7 +156,7 @@ namespace Eval {
exit(EXIT_FAILURE);
}
if (useNNUE)
if (useNNUE != UseNNUEMode::False)
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
else
sync_cout << "info string classical evaluation enabled" << sync_endl;
@@ -1082,7 +1096,19 @@ make_v:
Value Eval::evaluate(const Position& pos) {
pos.this_thread()->on_eval();
Value v;
if (NNUE::useNNUE == NNUE::UseNNUEMode::Pure) {
v = NNUE::evaluate(pos);
// Guarantee evaluation does not hit the tablebase range
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
return v;
}
// Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical,
// but we switch to NNUE during long shuffling or with high material on the board.
bool useClassical = (pos.this_thread()->depth > 9 || pos.count<ALL_PIECES>() > 7) &&
@@ -1090,14 +1116,14 @@ Value Eval::evaluate(const Position& pos) {
// Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical,
// but we switch to NNUE during long shuffling or with high material on the board.
if (!useNNUE || useClassical)
if (NNUE::useNNUE == NNUE::UseNNUEMode::False || useClassical)
{
v = Evaluation<NO_TRACE>(pos).value(); // classical
useClassical = abs(v) >= 297;
}
// If result of a classical evaluation is much lower than threshold fall back to NNUE
if (useNNUE && !useClassical)
if (NNUE::useNNUE != NNUE::UseNNUEMode::False && !useClassical)
{
Value nnue = NNUE::evaluate(pos, true); // NNUE
int scale = 1080 + 110 * pos.non_pawn_material() / 5120;
@@ -1171,14 +1197,14 @@ std::string Eval::trace(Position& pos) {
<< "| Total | " << Term(TOTAL)
<< "+------------+-------------+-------------+-------------+\n";
if (Eval::useNNUE)
if (NNUE::useNNUE != NNUE::UseNNUEMode::False)
ss << '\n' << NNUE::trace(pos) << '\n';
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n";
if (Eval::useNNUE)
if (NNUE::useNNUE != NNUE::UseNNUEMode::False)
{
v = NNUE::evaluate(pos, false);
v = pos.side_to_move() == WHITE ? v : -v;
@@ -1188,7 +1214,7 @@ std::string Eval::trace(Position& pos) {
v = evaluate(pos);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "Final evaluation " << to_cp(v) << " (white side)";
if (Eval::useNNUE)
if (NNUE::useNNUE != NNUE::UseNNUEMode::False)
ss << " [with scaled NNUE, hybrid, ...]";
ss << "\n";
+9 -3
View File
@@ -33,15 +33,21 @@ namespace Eval {
std::string trace(Position& pos);
Value evaluate(const Position& pos);
extern bool useNNUE;
extern std::string currentEvalFileName;
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-3c0aa92af1da.nnue"
namespace NNUE {
enum struct UseNNUEMode
{
False,
True,
Pure
};
extern UseNNUEMode useNNUE;
extern std::string currentEvalFileName;
std::string trace(Position& pos);
Value evaluate(const Position& pos, bool adjusted = false);
File diff suppressed because it is too large Load Diff
+2
View File
@@ -18,6 +18,8 @@
#include <iostream>
#include "nnue/evaluate_nnue.h"
#include "bitboard.h"
#include "endgame.h"
#include "position.h"
+28
View File
@@ -65,6 +65,8 @@ using namespace std;
namespace Stockfish {
SynchronizedRegionLogger sync_region_cout(std::cout);
namespace {
/// Version number. If Version is left empty, then compile date in the format
@@ -669,4 +671,30 @@ void init(int argc, char* argv[]) {
} // namespace CommandLine
// Returns a string that represents the current time. (Used when learning evaluation functions)
std::string now_string()
{
// Using std::ctime(), localtime() gives a warning that MSVC is not secure.
// This shouldn't happen in the C++ standard, but...
#if defined(_MSC_VER)
// C4996 : 'ctime' : This function or variable may be unsafe.Consider using ctime_s instead.
#pragma warning(disable : 4996)
#endif
auto now = std::chrono::system_clock::now();
auto tp = std::chrono::system_clock::to_time_t(now);
auto result = string(std::ctime(&tp));
// remove line endings if they are included at the end
while (*result.rbegin() == '\n' || (*result.rbegin() == '\r'))
result.pop_back();
return result;
}
void sleep(int ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
} // namespace Stockfish
+470
View File
@@ -19,12 +19,21 @@
#ifndef MISC_H_INCLUDED
#define MISC_H_INCLUDED
#include <algorithm>
#include <cassert>
#include <chrono>
#include <functional>
#include <mutex>
#include <ostream>
#include <string>
#include <vector>
#include <iostream>
#include <cstdint>
#include <cmath>
#include <cctype>
#include <sstream>
#include <deque>
#include "types.h"
@@ -44,6 +53,20 @@ void dbg_hit_on(bool c, bool b);
void dbg_mean_of(int v);
void dbg_print();
/// Debug macro to write to std::err if NDEBUG flag is set, and do nothing otherwise
#if defined(NDEBUG)
#define debug 1 && std::cerr
#else
#define debug 0 && std::cerr
#endif
inline void hit_any_key() {
#ifndef NDEBUG
debug << "Hit any key to continue..." << std::endl << std::flush;
system("read"); // on Windows, should be system("pause");
#endif
}
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
inline TimePoint now() {
@@ -138,6 +161,219 @@ private:
std::size_t size_ = 0;
};
// This logger allows printing many parts in a region atomically
// but doesn't block the threads trying to append to other regions.
// Instead if some region tries to pring while other region holds
// the lock the messages are queued to be printed as soon as the
// current region releases the lock.
struct SynchronizedRegionLogger
{
using RegionId = std::uint64_t;
struct Region
{
friend struct SynchronizedRegionLogger;
Region() :
logger(nullptr), region_id(0), is_held(false)
{
}
Region(const Region&) = delete;
Region& operator=(const Region&) = delete;
Region(Region&& other) :
logger(other.logger), region_id(other.region_id), is_held(other.is_held)
{
other.logger = nullptr;
other.is_held = false;
}
Region& operator=(Region&& other) {
if (is_held && logger != nullptr)
{
logger->release_region(region_id);
}
logger = other.logger;
region_id = other.region_id;
is_held = other.is_held;
other.is_held = false;
return *this;
}
~Region() { unlock(); }
void unlock() {
if (is_held) {
is_held = false;
if (logger != nullptr)
logger->release_region(region_id);
}
}
Region& operator << (std::ostream&(*pManip)(std::ostream&)) {
if (logger != nullptr)
logger->write(region_id, pManip);
return *this;
}
template <typename T>
Region& operator << (const T& value) {
if (logger != nullptr)
logger->write(region_id, value);
return *this;
}
private:
SynchronizedRegionLogger* logger;
RegionId region_id;
bool is_held;
Region(SynchronizedRegionLogger& log, RegionId id) :
logger(&log), region_id(id), is_held(true)
{
}
};
private:
struct RegionBookkeeping
{
RegionBookkeeping(RegionId rid) : id(rid), is_held(true) {}
std::vector<std::string> pending_parts;
RegionId id;
bool is_held;
};
RegionId init_next_region()
{
static RegionId next_id = 0;
std::lock_guard lock(mutex);
const auto id = next_id++;
regions.emplace_back(id);
return id;
}
void write(RegionId id, std::ostream&(*pManip)(std::ostream&)) {
std::lock_guard lock(mutex);
if (regions.empty())
return;
if (id == regions.front().id) {
// We can just directly print to the output because
// we are at the front of the region queue.
out << *pManip;
} else {
// We have to schedule the print until previous regions are
// processed
auto* region = find_region_nolock(id);
if (region == nullptr)
return;
std::stringstream ss;
ss << *pManip;
region->pending_parts.emplace_back(std::move(ss).str());
}
}
template <typename T>
void write(RegionId id, const T& value) {
std::lock_guard lock(mutex);
if (regions.empty())
return;
if (id == regions.front().id) {
// We can just directly print to the output because
// we are at the front of the region queue.
out << value;
} else {
// We have to schedule the print until previous regions are
// processed
auto* region = find_region_nolock(id);
if (region == nullptr)
return;
std::stringstream ss;
ss << value;
region->pending_parts.emplace_back(std::move(ss).str());
}
}
std::ostream& out;
std::deque<RegionBookkeeping> regions;
std::mutex mutex;
RegionBookkeeping* find_region_nolock(RegionId id) {
// Linear search because the amount of concurrent regions should be small.
auto it = std::find_if(
regions.begin(),
regions.end(),
[id](const RegionBookkeeping& r) { return r.id == id; });
if (it == regions.end())
return nullptr;
else
return &*it;
}
void release_region(RegionId id) {
std::lock_guard lock(mutex);
auto* region = find_region_nolock(id);
if (region == nullptr)
return;
region->is_held = false;
process_backlog_nolock();
}
void process_backlog_nolock()
{
while(!regions.empty()) {
auto& region = regions.front();
for(auto& part : region.pending_parts) {
out << part;
}
// If the region is still held then we don't
// want to start printing stuff from the next region.
if (region.is_held)
break;
regions.pop_front();
}
}
public:
SynchronizedRegionLogger(std::ostream& s) :
out(s)
{
}
[[nodiscard]] Region new_region() {
const auto id = init_next_region();
return Region(*this, id);
}
};
extern SynchronizedRegionLogger sync_region_cout;
/// sigmoid(t, x0, y0, C, P, Q) implements a sigmoid-like function using only integers,
/// with the following properties:
@@ -181,6 +417,19 @@ inline int64_t sigmoid(int64_t t, int64_t x0,
/// For further analysis see
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
static uint64_t string_hash(const std::string& str)
{
uint64_t h = 525201411107845655ull;
for (auto c : str) {
h ^= static_cast<uint64_t>(c);
h *= 0x5bd1e9955bd1e995ull;
h ^= h >> 47;
}
return h;
}
class PRNG {
uint64_t s;
@@ -192,7 +441,9 @@ class PRNG {
}
public:
PRNG() { set_seed_from_time(); }
PRNG(uint64_t seed) : s(seed) { assert(seed); }
PRNG(const std::string& seed) { set_seed(seed); }
template<typename T> T rand() { return T(rand64()); }
@@ -200,8 +451,54 @@ public:
/// Output values only have 1/8th of their bits set on average.
template<typename T> T sparse_rand()
{ return T(rand64() & rand64() & rand64()); }
// Returns a random number from 0 to n-1. (Not uniform distribution, but this is enough in reality)
uint64_t rand(uint64_t n) { return rand<uint64_t>() % n; }
// Return the random seed used internally.
uint64_t get_seed() const { return s; }
void set_seed(uint64_t seed) { s = seed; }
uint64_t next_random_seed()
{
uint64_t seed = 0;
for(int i = 0; i < 64; ++i)
{
const auto off = rand64() % 64;
seed |= (rand64() & (uint64_t(1) << off)) >> off;
seed <<= 1;
}
return seed;
}
void set_seed_from_time()
{
set_seed(std::chrono::system_clock::now().time_since_epoch().count());
}
void set_seed(const std::string& str)
{
if (str.empty())
{
set_seed_from_time();
}
else if (std::all_of(str.begin(), str.end(), [](char c) { return std::isdigit(c);} )) {
set_seed(std::stoull(str));
}
else
{
set_seed(string_hash(str));
}
}
};
// Display a random seed. (For debugging)
inline std::ostream& operator<<(std::ostream& os, PRNG& prng)
{
os << "PRNG::seed = " << std::hex << prng.get_seed() << std::dec;
return os;
}
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ typedef unsigned __int128 uint128;
@@ -216,6 +513,74 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#endif
}
// This bitset can be accessed concurrently, provided
// the concurrent accesses are performed on distinct
// instances of underlying type. That means the cuncurrent
// accesses need to be spaced by at least
// bits_per_bucket bits.
// But at least best_concurrent_access_stride bits
// is recommended to prevent false sharing.
template <uint64_t N>
struct LargeBitset
{
private:
constexpr static uint64_t cache_line_size = 64;
public:
using UnderlyingType = uint64_t;
constexpr static uint64_t num_bits = N;
constexpr static uint64_t bits_per_bucket = 8 * sizeof(uint64_t);
constexpr static uint64_t num_buckets = (num_bits + bits_per_bucket - 1) / bits_per_bucket;
constexpr static uint64_t best_concurrent_access_stride = 8 * cache_line_size;
LargeBitset()
{
std::fill(std::begin(bits), std::end(bits), 0);
}
void set(uint64_t idx)
{
const uint64_t bucket = idx / bits_per_bucket;
const uint64_t bit = uint64_t(1) << (idx % bits_per_bucket);
bits[bucket] |= bit;
}
bool test(uint64_t idx) const
{
const uint64_t bucket = idx / bits_per_bucket;
const uint64_t bit = uint64_t(1) << (idx % bits_per_bucket);
return bits[bucket] & bit;
}
uint64_t count() const
{
uint64_t c = 0;
uint64_t i = 0;
for (; i < num_buckets - 3; i += 4)
{
uint64_t c0 = popcount(bits[i+0]);
uint64_t c1 = popcount(bits[i+1]);
uint64_t c2 = popcount(bits[i+2]);
uint64_t c3 = popcount(bits[i+3]);
c0 += c1;
c2 += c3;
c += c0 + c2;
}
for (; i < num_buckets; ++i)
{
c += popcount(bits[i]);
}
return c;
}
private:
alignas(cache_line_size) UnderlyingType bits[num_buckets];
};
/// Under Windows it is not possible for a process to run on more than one
/// logical processor group. This usually means to be limited to use max 64
/// cores. To overcome this, some special platform specific API should be
@@ -226,6 +591,111 @@ namespace WinProcGroup {
void bindThisThread(size_t idx);
}
// Returns a string that represents the current time. (Used for log output when learning evaluation function)
std::string now_string();
void sleep(int ms);
namespace Algo {
// Fisher-Yates
template <typename Rng, typename T>
void shuffle(std::vector<T>& buf, Rng&& prng)
{
const auto size = buf.size();
for (uint64_t i = 0; i < size; ++i)
std::swap(buf[i], buf[prng.rand(size - i) + i]);
}
// split the string
inline std::vector<std::string> split(const std::string& input, char delimiter) {
std::istringstream stream(input);
std::string field;
std::vector<std::string> fields;
while (std::getline(stream, field, delimiter)) {
fields.push_back(field);
}
return fields;
}
}
// --------------------
// Path
// --------------------
// Something like Path class in C#. File name manipulation.
// Match with the C# method name.
struct Path
{
// Combine the path name and file name and return it.
// If the folder name is not an empty string, append it if there is no'/' or'\\' at the end.
static std::string combine(const std::string& folder, const std::string& filename)
{
if (folder.length() >= 1 && *folder.rbegin() != '/' && *folder.rbegin() != '\\')
return folder + "/" + filename;
return folder + filename;
}
// Get the file name part (excluding the folder name) from the full path expression.
static std::string get_file_name(const std::string& path)
{
// I don't know which "\" or "/" is used.
auto path_index1 = path.find_last_of("\\") + 1;
auto path_index2 = path.find_last_of("/") + 1;
auto path_index = std::max(path_index1, path_index2);
return path.substr(path_index);
}
};
// It is ignored when new even though alignas is specified & because it is ignored when the STL container allocates memory,
// A custom allocator used for that.
template <typename T>
class AlignedAllocator {
public:
using value_type = T;
AlignedAllocator() {}
AlignedAllocator(const AlignedAllocator&) {}
AlignedAllocator(AlignedAllocator&&) {}
template <typename U> AlignedAllocator(const AlignedAllocator<U>&) {}
T* allocate(std::size_t n) { return (T*)std_aligned_alloc(alignof(T), n * sizeof(T)); }
void deallocate(T* p, std::size_t ) { std_aligned_free(p); }
};
template <typename T>
class CacheLineAlignedAllocator {
public:
using value_type = T;
constexpr static uint64_t cache_line_size = 64;
CacheLineAlignedAllocator() {}
CacheLineAlignedAllocator(const CacheLineAlignedAllocator&) {}
CacheLineAlignedAllocator(CacheLineAlignedAllocator&&) {}
template <typename U> CacheLineAlignedAllocator(const CacheLineAlignedAllocator<U>&) {}
T* allocate(std::size_t n) { return (T*)std_aligned_alloc(cache_line_size, n * sizeof(T)); }
void deallocate(T* p, std::size_t) { std_aligned_free(p); }
};
// --------------------
// Dependency Wrapper
// --------------------
namespace Dependency
{
// In the Linux environment, if you getline() the text file is'\r\n'
// Since'\r' remains at the end, write a wrapper to remove this'\r'.
// So when calling getline() on fstream,
// just write getline() instead of std::getline() and use this function.
extern bool getline(std::ifstream& fs, std::string& s);
}
namespace CommandLine {
void init(int argc, char* argv[]);
+3
View File
@@ -68,6 +68,9 @@ struct MoveList {
return std::find(begin(), end(), move) != end();
}
// returns the i th element
const ExtMove at(size_t i) const { assert(0 <= i && i < size()); return begin()[i]; }
private:
ExtMove moveList[MAX_MOVES], *last;
};
View File
+2
View File
@@ -21,6 +21,8 @@
#ifndef NNUE_COMMON_H_INCLUDED
#define NNUE_COMMON_H_INCLUDED
#include "../types.h"
#include <cstring>
#include <iostream>
+3
View File
@@ -24,6 +24,9 @@
#include "nnue_common.h"
#include "nnue_architecture.h"
#include "../misc.h"
#include "../position.h"
#include <cstring> // std::memset()
namespace Stockfish::Eval::NNUE {
+40 -4
View File
@@ -23,6 +23,8 @@
#include <iomanip>
#include <sstream>
#include "nnue/evaluate_nnue.h"
#include "bitboard.h"
#include "misc.h"
#include "movegen.h"
@@ -32,6 +34,9 @@
#include "uci.h"
#include "syzygy/tbprobe.h"
#include "tools/packed_sfen.h"
#include "tools/sfen_packer.h"
using std::string;
namespace Stockfish {
@@ -754,7 +759,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
else
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
if (Eval::useNNUE)
if (Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
{
dp.dirty_num = 2; // 1 piece moved, 1 piece captured
dp.piece[1] = captured;
@@ -798,7 +803,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
// Move the piece. The tricky Chess960 castling is handled earlier
if (type_of(m) != CASTLING)
{
if (Eval::useNNUE)
if (Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
{
dp.piece[0] = pc;
dp.from[0] = from;
@@ -829,7 +834,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
remove_piece(to);
put_piece(promotion, to);
if (Eval::useNNUE)
if (Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
{
// Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
dp.to[0] = SQ_NONE;
@@ -967,7 +972,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
if (Do && Eval::useNNUE)
if (Do && Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
{
auto& dp = st->dirtyPiece;
dp.piece[0] = make_piece(us, KING);
@@ -1001,6 +1006,7 @@ void Position::do_null_move(StateInfo& newSt) {
newSt.previous = st;
st = &newSt;
// Used by NNUE
st->dirtyPiece.dirty_num = 0;
st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
st->accumulator.computed[WHITE] = false;
@@ -1177,6 +1183,22 @@ bool Position::is_draw(int ply) const {
}
/// Position::is_fifty_move_draw() returns true if a game can be claimed
/// by a fifty-move draw rule.
bool Position::is_fifty_move_draw() const {
return (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()));
}
/// Position::is_three_fold_repetition() returns true if there is 3-fold repetition.
bool Position::is_three_fold_repetition() const {
return st->repetition < 0;
}
// Position::has_repeated() tests whether there has been at least one repetition
// of positions since the last capture or pawn move.
@@ -1345,4 +1367,18 @@ bool Position::pos_is_ok() const {
return true;
}
// Add a function that directly unpacks for speed. It's pretty tough.
// Write it by combining packer::unpack() and Position::set().
// If there is a problem with the passed phase and there is an error, non-zero is returned.
int Position::set_from_packed_sfen(const Tools::PackedSfen& sfen , StateInfo* si, Thread* th, bool frc)
{
return Tools::set_from_packed_sfen(*this, sfen, si, th, frc);
}
// Get the packed sfen. Returns to the buffer specified in the argument.
void Position::sfen_pack(Tools::PackedSfen& sfen, bool resetCastlingRights)
{
sfen = Tools::sfen_pack(*this, resetCastlingRights);
}
} // namespace Stockfish
+35
View File
@@ -31,6 +31,9 @@
#include "nnue/nnue_accumulator.h"
#include "tools/packed_sfen.h"
#include "tools/sfen_packer.h"
namespace Stockfish {
/// StateInfo struct stores information needed to restore a Position object to
@@ -126,6 +129,7 @@ public:
bool legal(Move m) const;
bool pseudo_legal(const Move m) const;
bool capture(Move m) const;
bool capture_or_promotion(Move m) const;
bool gives_check(Move m) const;
Piece moved_piece(Move m) const;
Piece captured_piece() const;
@@ -157,6 +161,8 @@ public:
bool is_chess960() const;
Thread* this_thread() const;
bool is_draw(int ply) const;
bool is_fifty_move_draw() const;
bool is_three_fold_repetition() const;
bool has_game_cycle(int ply) const;
bool has_repeated() const;
int rule50_count() const;
@@ -171,6 +177,28 @@ public:
// Used by NNUE
StateInfo* state() const;
// --sfenization helper
friend int Tools::set_from_packed_sfen(Position& pos, const Tools::PackedSfen& sfen, StateInfo* si, Thread* th, bool frc);
// Get the packed sfen. Returns to the buffer specified in the argument.
// Do not include gamePly in pack.
void sfen_pack(Tools::PackedSfen& sfen, bool resetCastlingRights);
// It is slow to go through sfen, so I made a function to set packed sfen directly.
// Equivalent to pos.set(sfen_unpack(data),si,th);.
// If there is a problem with the passed phase and there is an error, non-zero is returned.
// PackedSfen does not include gamePly so it cannot be restored. If you want to set it, specify it with an argument.
int set_from_packed_sfen(const Tools::PackedSfen& sfen, StateInfo* si, Thread* th, bool frc);
void clear() { std::memset(this, 0, sizeof(Position)); }
// Give the board, hand piece, and turn, and return the sfen.
//static std::string sfen_from_rawdata(Piece board[81], Hand hands[2], Color turn, int gamePly);
// Returns the position of the ball on the c side.
Square king_square(Color c) const { return lsb(pieces(c, KING)); }
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
@@ -368,6 +396,11 @@ inline bool Position::is_chess960() const {
return chess960;
}
inline bool Position::capture_or_promotion(Move m) const {
assert(is_ok(m));
return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m));
}
inline bool Position::capture(Move m) const {
assert(is_ok(m));
// Castling is encoded as "king captures rook"
@@ -425,6 +458,8 @@ inline StateInfo* Position::state() const {
return st;
}
static const char* const StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
} // namespace Stockfish
#endif // #ifndef POSITION_H_INCLUDED
+1238 -34
View File
File diff suppressed because it is too large Load Diff
+35 -1
View File
@@ -24,6 +24,7 @@
#include "misc.h"
#include "movepick.h"
#include "types.h"
#include "uci.h"
namespace Stockfish {
@@ -34,6 +35,7 @@ namespace Search {
/// Threshold used for countermoves based pruning
constexpr int CounterMovePruneThreshold = 0;
extern bool prune_at_shallow_depth;
/// Stack struct keeps track of the information we need to remember from nodes
/// shallower and deeper in the tree during the search. Each search thread has
@@ -92,6 +94,7 @@ struct LimitsType {
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
movestogo = depth = mate = perft = infinite = 0;
nodes = 0;
silent = false;
}
bool use_time_management() const {
@@ -102,6 +105,9 @@ struct LimitsType {
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
int64_t nodes;
// Silent mode that does not output to the screen (for continuous self-play in process)
// Do not output PV at this time.
bool silent;
};
extern LimitsType Limits;
@@ -109,7 +115,35 @@ extern LimitsType Limits;
void init();
void clear();
} // namespace Search
// A pair of reader and evaluation value. Returned by Tools::search(),Tools::qsearch().
using ValueAndPV = std::pair<Value, std::vector<Move>>;
ValueAndPV qsearch(Position& pos);
ValueAndPV search(Position& pos, int depth_, size_t multiPV = 1, uint64_t nodesLimit = 0);
namespace MCTS {
struct MctsContinuation {
std::uint64_t numVisits;
Value value;
float actionValue;
std::vector<Move> pv;
};
ValueAndPV search_mcts(
Position& pos,
std::uint64_t nodes,
Depth leafDepth,
float explorationFactor);
std::vector<MctsContinuation> search_mcts_multipv(
Position& pos,
std::uint64_t numPlayouts,
Depth leafDepth,
float explorationFactor);
}
}
} // namespace Stockfish
+1 -1
View File
@@ -52,7 +52,7 @@
using namespace Stockfish::Tablebases;
int Stockfish::Tablebases::MaxCardinality;
int Stockfish::Tablebases::MaxCardinality = 0;
namespace Stockfish {
+41 -5
View File
@@ -34,9 +34,10 @@ ThreadPool Threads; // Global object
/// Thread constructor launches the thread and waits until it goes to sleep
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this), maxNodes(0) {
wait_for_search_finished();
wait_for_worker_finished();
}
@@ -82,6 +83,14 @@ void Thread::start_searching() {
cv.notify_one(); // Wake up the thread in idle_loop()
}
void Thread::execute_with_worker(std::function<void(Thread&)> t)
{
std::lock_guard<std::mutex> lk(mutex);
worker = std::move(t);
searching = true;
cv.notify_one(); // Wake up the thread in idle_loop()
}
/// Thread::wait_for_search_finished() blocks on the condition variable
/// until the thread has finished searching.
@@ -93,6 +102,12 @@ void Thread::wait_for_search_finished() {
}
void Thread::wait_for_worker_finished() {
std::unique_lock<std::mutex> lk(mutex);
cv.wait(lk, [&]{ return !searching; });
}
/// Thread::idle_loop() is where the thread is parked, blocked on the
/// condition variable, when it has no work to do.
@@ -110,15 +125,25 @@ void Thread::idle_loop() {
{
std::unique_lock<std::mutex> lk(mutex);
searching = false;
worker = nullptr;
cv.notify_one(); // Wake up anyone waiting for search finished
cv.wait(lk, [&]{ return searching; });
if (exit)
return;
auto wrk = std::move(worker);
lk.unlock();
search();
if (wrk)
{
wrk(*this);
}
else
{
search();
}
}
}
@@ -166,6 +191,13 @@ void ThreadPool::clear() {
main()->previousTimeReduction = 1.0;
}
void ThreadPool::execute_with_workers(const std::function<void(Thread&)>& worker)
{
for(Thread* th : *this)
{
th->execute_with_worker(worker);
}
}
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
/// returns immediately. Main thread will wake up other threads and start the search.
@@ -186,9 +218,6 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
rootMoves.emplace_back(m);
if (!rootMoves.empty())
Tablebases::rank_root_moves(pos, rootMoves);
// After ownership transfer 'states' becomes empty, so if we stop the search
// and call 'go' again without setting a new position states.get() == NULL.
assert(states.get() || setupStates.get());
@@ -264,4 +293,11 @@ void ThreadPool::wait_for_search_finished() const {
th->wait_for_search_finished();
}
void ThreadPool::wait_for_workers_finished() const {
for (Thread* th : *this)
th->wait_for_worker_finished();
}
} // namespace Stockfish
+90
View File
@@ -24,6 +24,7 @@
#include <mutex>
#include <thread>
#include <vector>
#include <functional>
#include "material.h"
#include "movepick.h"
@@ -39,24 +40,51 @@ namespace Stockfish {
/// pointer to an entry its life time is unlimited and we don't have
/// to care about someone changing the entry under our feet.
namespace Detail {
template <typename T>
struct TypeIdentity {
using Type = T;
};
}
class Thread {
std::mutex mutex;
std::condition_variable cv;
size_t idx;
bool exit = false, searching = true; // Set before starting std::thread
std::function<void(Thread&)> worker;
std::function<void(Position&)> on_eval_callback;
NativeThread stdThread;
public:
explicit Thread(size_t);
virtual ~Thread();
virtual void search();
// The function object to be executed is taken by value to remove
// the need for separate lvalue and rvalue overloads.
// The worker thread needs to have ownership of the task
// to be executed because otherwise there's no way to manage its lifetime.
virtual void execute_with_worker(std::function<void(Thread&)> t);
void clear();
void idle_loop();
void start_searching();
void wait_for_search_finished();
size_t id() const { return idx; }
void wait_for_worker_finished();
template <typename FuncT>
void set_eval_callback(FuncT&& f) { on_eval_callback = std::forward<FuncT>(f); }
void clear_eval_callback() { on_eval_callback = nullptr; }
void on_eval() { if (on_eval_callback) on_eval_callback(rootPos); }
Pawns::Table pawnsTable;
Material::Table materialTable;
size_t pvIdx, pvLast;
@@ -65,6 +93,7 @@ public:
int selDepth, nmpMinPly;
Color nmpColor;
Value bestValue, optimism[COLOR_NB];
uint64_t maxNodes;
Position rootPos;
StateInfo rootState;
@@ -76,6 +105,11 @@ public:
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
Score trend;
int failedHighCnt;
bool rootInTB;
int Cardinality;
bool UseRule50;
Depth ProbeDepth;
};
@@ -104,6 +138,61 @@ struct MainThread : public Thread {
struct ThreadPool : public std::vector<Thread*> {
// Each thread gets its own copy of the `worker` function object.
// This means that each worker thread will have exclusive access
// to the state of the `worker` function object.
void execute_with_workers(const std::function<void(Thread&)>& worker);
template <typename IndexT, typename FuncT>
void for_each_index_with_workers(
IndexT begin,
typename Detail::TypeIdentity<IndexT>::Type end,
FuncT func)
{
// This value must outlive the function call.
// It's fairly safe if we make it static
// because for_each_index_with_workers
// is not reentrant nor thread safe.
static std::atomic<IndexT> i_atomic;
i_atomic.store(begin);
execute_with_workers(
[end, func](Thread& th) mutable {
for(;;) {
const auto i = i_atomic.fetch_add(1);
if (i >= end)
break;
func(th, i);
}
});
}
template <typename IndexT, typename FuncT>
void for_each_index_chunk_with_workers(
IndexT begin,
typename Detail::TypeIdentity<IndexT>::Type end,
FuncT func)
{
// This value must outlive the function call.
// It's fairly safe if we make it static
// because for_each_index_with_workers
// is not reentrant nor thread safe.
const IndexT size = end - begin;
const IndexT chunk_size = (size + this->size()) / this->size();
execute_with_workers(
[chunk_size, end, func](Thread& th) mutable {
const IndexT thread_id = th.id();
const IndexT offset = chunk_size * thread_id;
if (offset >= end)
return;
const IndexT count = offset + chunk_size > end ? end - offset : chunk_size;
func(th, offset, count);
});
}
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
void clear();
void set(size_t);
@@ -114,6 +203,7 @@ struct ThreadPool : public std::vector<Thread*> {
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
void wait_for_workers_finished() const;
std::atomic_bool stop, increaseDepth;
+815
View File
@@ -0,0 +1,815 @@
#include "convert.h"
#include "uci.h"
#include "misc.h"
#include "thread.h"
#include "position.h"
#include "tt.h"
#include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue.h"
#include "syzygy/tbprobe.h"
#include <sstream>
#include <fstream>
#include <unordered_set>
#include <iomanip>
#include <list>
#include <cmath> // std::exp(),std::pow(),std::log()
#include <cstring> // memcpy()
#include <memory>
#include <limits>
#include <optional>
#include <chrono>
#include <random>
#include <regex>
#include <filesystem>
using namespace std;
namespace sys = std::filesystem;
namespace Stockfish::Tools
{
bool fen_is_ok(Position& pos, std::string input_fen) {
std::string pos_fen = pos.fen();
std::istringstream ss_input(input_fen);
std::istringstream ss_pos(pos_fen);
// example : "2r4r/4kpp1/nb1np3/p2p3p/B2P1BP1/PP6/4NPKP/2R1R3 w - h6 0 24"
// --> "2r4r/4kpp1/nb1np3/p2p3p/B2P1BP1/PP6/4NPKP/2R1R3"
std::string str_input, str_pos;
ss_input >> str_input;
ss_pos >> str_pos;
// Only compare "Piece placement field" between input_fen and pos.fen().
return str_input == str_pos;
}
void convert_bin(
const vector<string>& filenames,
const string& output_file_name,
const int ply_minimum,
const int ply_maximum,
const int interpolate_eval,
const int src_score_min_value,
const int src_score_max_value,
const int dest_score_min_value,
const int dest_score_max_value,
const bool check_invalid_fen,
const bool check_illegal_move)
{
std::cout << "check_invalid_fen=" << check_invalid_fen << std::endl;
std::cout << "check_illegal_move=" << check_illegal_move << std::endl;
std::fstream fs;
uint64_t data_size = 0;
uint64_t filtered_size = 0;
uint64_t filtered_size_fen = 0;
uint64_t filtered_size_move = 0;
uint64_t filtered_size_ply = 0;
auto th = Threads.main();
auto& tpos = th->rootPos;
// convert plain rag to packed sfenvalue for Yaneura king
fs.open(output_file_name, ios::app | ios::binary);
StateListPtr states;
for (auto filename : filenames) {
std::cout << "convert " << filename << " ... ";
std::string line;
ifstream ifs;
ifs.open(filename);
PackedSfenValue p;
data_size = 0;
filtered_size = 0;
filtered_size_fen = 0;
filtered_size_move = 0;
filtered_size_ply = 0;
p.gamePly = 1; // Not included in apery format. Should be initialized
bool ignore_flag_fen = false;
bool ignore_flag_move = false;
bool ignore_flag_ply = false;
while (std::getline(ifs, line)) {
std::stringstream ss(line);
std::string token;
std::string value;
ss >> token;
if (token == "fen") {
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
std::string input_fen = line.substr(4);
tpos.set(input_fen, false, &states->back(), Threads.main());
if (check_invalid_fen && !fen_is_ok(tpos, input_fen)) {
ignore_flag_fen = true;
filtered_size_fen++;
}
else {
tpos.sfen_pack(p.sfen, false);
}
}
else if (token == "move") {
ss >> value;
Move move = UCI::to_move(tpos, value);
if (check_illegal_move && move == MOVE_NONE) {
ignore_flag_move = true;
filtered_size_move++;
}
else {
p.move = move;
}
}
else if (token == "score") {
double score;
ss >> score;
// Training Formula ?Issue #71 ?nodchip/Stockfish https://github.com/nodchip/Stockfish/issues/71
// Normalize to [0.0, 1.0].
score = (score - src_score_min_value) / (src_score_max_value - src_score_min_value);
// Scale to [dest_score_min_value, dest_score_max_value].
score = score * (dest_score_max_value - dest_score_min_value) + dest_score_min_value;
p.score = std::clamp((int32_t)std::round(score), -(int32_t)VALUE_MATE, (int32_t)VALUE_MATE);
}
else if (token == "ply") {
int temp;
ss >> temp;
if (temp < ply_minimum || temp > ply_maximum) {
ignore_flag_ply = true;
filtered_size_ply++;
}
p.gamePly = uint16_t(temp); // No cast here?
if (interpolate_eval != 0) {
p.score = min(3000, interpolate_eval * temp);
}
}
else if (token == "result") {
int temp;
ss >> temp;
p.game_result = int8_t(temp); // Do you need a cast here?
if (interpolate_eval) {
p.score = p.score * p.game_result;
}
}
else if (token == "e") {
if (!(ignore_flag_fen || ignore_flag_move || ignore_flag_ply)) {
fs.write((char*)&p, sizeof(PackedSfenValue));
data_size += 1;
// debug
// std::cout<<tpos<<std::endl;
// std::cout<<p.score<<","<<int(p.gamePly)<<","<<int(p.game_result)<<std::endl;
}
else {
filtered_size++;
}
ignore_flag_fen = false;
ignore_flag_move = false;
ignore_flag_ply = false;
}
}
std::cout << "done " << data_size << " parsed " << filtered_size << " is filtered"
<< " (invalid fen:" << filtered_size_fen << ", illegal move:" << filtered_size_move << ", invalid ply:" << filtered_size_ply << ")" << std::endl;
ifs.close();
}
std::cout << "all done" << std::endl;
fs.close();
}
static inline void ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}
static inline void rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
static inline void trim(std::string& s) {
ltrim(s);
rtrim(s);
}
int parse_game_result_from_pgn_extract(std::string result) {
// White Win
if (result == "\"1-0\"") {
return 1;
}
// Black Win
else if (result == "\"0-1\"") {
return -1;
}
// Draw
else {
return 0;
}
}
// 0.25 --> 0.25 * PawnValueEg
// #-4 --> -mate_in(4)
// #3 --> mate_in(3)
// -M4 --> -mate_in(4)
// +M3 --> mate_in(3)
Value parse_score_from_pgn_extract(std::string eval, bool& success) {
success = true;
if (eval.substr(0, 1) == "#") {
if (eval.substr(1, 1) == "-") {
return -mate_in(stoi(eval.substr(2, eval.length() - 2)));
}
else {
return mate_in(stoi(eval.substr(1, eval.length() - 1)));
}
}
else if (eval.substr(0, 2) == "-M") {
//std::cout << "eval=" << eval << std::endl;
return -mate_in(stoi(eval.substr(2, eval.length() - 2)));
}
else if (eval.substr(0, 2) == "+M") {
//std::cout << "eval=" << eval << std::endl;
return mate_in(stoi(eval.substr(2, eval.length() - 2)));
}
else {
char* endptr;
double value = strtod(eval.c_str(), &endptr);
if (*endptr != '\0') {
success = false;
return VALUE_ZERO;
}
else {
return Value(value * static_cast<double>(PawnValueEg));
}
}
}
// for Debug
//#define DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT
bool is_like_fen(std::string fen) {
int count_space = std::count(fen.cbegin(), fen.cend(), ' ');
int count_slash = std::count(fen.cbegin(), fen.cend(), '/');
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
//std::cout << "count_space=" << count_space << std::endl;
//std::cout << "count_slash=" << count_slash << std::endl;
#endif
return count_space == 5 && count_slash == 7;
}
void convert_bin_from_pgn_extract(
const vector<string>& filenames,
const string& output_file_name,
const bool pgn_eval_side_to_move,
const bool convert_no_eval_fens_as_score_zero)
{
std::cout << "pgn_eval_side_to_move=" << pgn_eval_side_to_move << std::endl;
std::cout << "convert_no_eval_fens_as_score_zero=" << convert_no_eval_fens_as_score_zero << std::endl;
auto th = Threads.main();
auto& pos = th->rootPos;
std::fstream ofs;
ofs.open(output_file_name, ios::out | ios::binary);
int game_count = 0;
int fen_count = 0;
for (auto filename : filenames) {
std::cout << now_string() << " convert " << filename << std::endl;
ifstream ifs;
ifs.open(filename);
int game_result = 0;
std::string line;
while (std::getline(ifs, line)) {
if (line.empty()) {
continue;
}
else if (line.substr(0, 1) == "[") {
std::regex pattern_result(R"(\[Result (.+?)\])");
std::smatch match;
// example: [Result "1-0"]
if (std::regex_search(line, match, pattern_result)) {
game_result = parse_game_result_from_pgn_extract(match.str(1));
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
std::cout << "game_result=" << game_result << std::endl;
#endif
game_count++;
if (game_count % 10000 == 0) {
std::cout << now_string() << " game_count=" << game_count << ", fen_count=" << fen_count << std::endl;
}
}
continue;
}
else {
int gamePly = 1;
auto itr = line.cbegin();
while (true) {
gamePly++;
PackedSfenValue psv;
memset((char*)&psv, 0, sizeof(PackedSfenValue));
// fen
{
bool fen_found = false;
while (!fen_found) {
std::regex pattern_bracket(R"(\{(.+?)\})");
std::smatch match;
if (!std::regex_search(itr, line.cend(), match, pattern_bracket)) {
break;
}
itr += match.position(0) + match.length(0) - 1;
std::string str_fen = match.str(1);
trim(str_fen);
if (is_like_fen(str_fen)) {
fen_found = true;
StateInfo si;
pos.set(str_fen, false, &si, th);
pos.sfen_pack(psv.sfen, false);
}
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
std::cout << "str_fen=" << str_fen << std::endl;
std::cout << "fen_found=" << fen_found << std::endl;
#endif
}
if (!fen_found) {
break;
}
}
// move
{
std::regex pattern_move(R"(\}(.+?)\{)");
std::smatch match;
if (!std::regex_search(itr, line.cend(), match, pattern_move)) {
break;
}
itr += match.position(0) + match.length(0) - 1;
std::string str_move = match.str(1);
trim(str_move);
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
std::cout << "str_move=" << str_move << std::endl;
#endif
psv.move = UCI::to_move(pos, str_move);
}
// eval
bool eval_found = false;
{
std::regex pattern_bracket(R"(\{(.+?)\})");
std::smatch match;
if (!std::regex_search(itr, line.cend(), match, pattern_bracket)) {
break;
}
std::string str_eval_clk = match.str(1);
trim(str_eval_clk);
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
std::cout << "str_eval_clk=" << str_eval_clk << std::endl;
#endif
// example: { [%eval 0.25] [%clk 0:10:00] }
// example: { [%eval #-4] [%clk 0:10:00] }
// example: { [%eval #3] [%clk 0:10:00] }
// example: { +0.71/22 1.2s }
// example: { -M4/7 0.003s }
// example: { M3/245 0.017s }
// example: { +M1/245 0.010s, White mates }
// example: { 0.60 }
// example: { book }
// example: { rnbqkb1r/pp3ppp/2p1pn2/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R w KQkq - 0 5 }
// Considering the absence of eval
if (!is_like_fen(str_eval_clk)) {
itr += match.position(0) + match.length(0) - 1;
if (str_eval_clk != "book") {
std::regex pattern_eval1(R"(\[\%eval (.+?)\])");
std::regex pattern_eval2(R"((.+?)\/)");
std::string str_eval;
if (std::regex_search(str_eval_clk, match, pattern_eval1) ||
std::regex_search(str_eval_clk, match, pattern_eval2)) {
str_eval = match.str(1);
trim(str_eval);
}
else {
str_eval = str_eval_clk;
}
bool success = false;
Value value = parse_score_from_pgn_extract(str_eval, success);
if (success) {
eval_found = true;
psv.score = std::clamp(value, -VALUE_MATE, VALUE_MATE);
}
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
std::cout << "str_eval=" << str_eval << std::endl;
std::cout << "success=" << success << ", psv.score=" << psv.score << std::endl;
#endif
}
}
}
// write
if (eval_found || convert_no_eval_fens_as_score_zero) {
if (!eval_found && convert_no_eval_fens_as_score_zero) {
psv.score = 0;
}
psv.gamePly = gamePly;
psv.game_result = game_result;
if (pos.side_to_move() == BLACK) {
if (!pgn_eval_side_to_move) {
psv.score *= -1;
}
psv.game_result *= -1;
}
ofs.write((char*)&psv, sizeof(PackedSfenValue));
fen_count++;
}
}
game_result = 0;
}
}
}
std::cout << now_string() << " game_count=" << game_count << ", fen_count=" << fen_count << std::endl;
std::cout << now_string() << " all done" << std::endl;
ofs.close();
}
void convert_plain(
const vector<string>& filenames,
const string& output_file_name)
{
Position tpos;
std::ofstream ofs;
ofs.open(output_file_name, ios::app);
auto th = Threads.main();
for (auto filename : filenames) {
std::cout << "convert " << filename << " ... ";
// Just convert packedsfenvalue to text
std::fstream fs;
fs.open(filename, ios::in | ios::binary);
PackedSfenValue p;
while (true)
{
if (fs.read((char*)&p, sizeof(PackedSfenValue))) {
StateInfo si;
tpos.set_from_packed_sfen(p.sfen, &si, th, false);
// write as plain text
ofs << "fen " << tpos.fen() << std::endl;
ofs << "move " << UCI::move(Move(p.move), false) << std::endl;
ofs << "score " << p.score << std::endl;
ofs << "ply " << int(p.gamePly) << std::endl;
ofs << "result " << int(p.game_result) << std::endl;
ofs << "e" << std::endl;
}
else {
break;
}
}
fs.close();
std::cout << "done" << std::endl;
}
ofs.close();
std::cout << "all done" << std::endl;
}
static inline const std::string plain_extension = ".plain";
static inline const std::string bin_extension = ".bin";
static inline const std::string binpack_extension = ".binpack";
static bool file_exists(const std::string& name)
{
std::ifstream f(name);
return f.good();
}
static bool ends_with(const std::string& lhs, const std::string& end)
{
if (end.size() > lhs.size()) return false;
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
}
static bool is_convert_of_type(
const std::string& input_path,
const std::string& output_path,
const std::string& expected_input_extension,
const std::string& expected_output_extension)
{
return ends_with(input_path, expected_input_extension)
&& ends_with(output_path, expected_output_extension);
}
using ConvertFunctionType = void(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate);
static ConvertFunctionType* get_convert_function(const std::string& input_path, const std::string& output_path)
{
if (is_convert_of_type(input_path, output_path, plain_extension, bin_extension))
return binpack::convertPlainToBin;
if (is_convert_of_type(input_path, output_path, plain_extension, binpack_extension))
return binpack::convertPlainToBinpack;
if (is_convert_of_type(input_path, output_path, bin_extension, plain_extension))
return binpack::convertBinToPlain;
if (is_convert_of_type(input_path, output_path, bin_extension, binpack_extension))
return binpack::convertBinToBinpack;
if (is_convert_of_type(input_path, output_path, binpack_extension, plain_extension))
return binpack::convertBinpackToPlain;
if (is_convert_of_type(input_path, output_path, binpack_extension, bin_extension))
return binpack::convertBinpackToBin;
return nullptr;
}
static void convert(const std::string& input_path, const std::string& output_path, std::ios_base::openmode om, bool validate)
{
if(!file_exists(input_path))
{
std::cerr << "Input file does not exist.\n";
return;
}
auto func = get_convert_function(input_path, output_path);
if (func != nullptr)
{
func(input_path, output_path, om, validate);
}
else
{
std::cerr << "Conversion between files of these types is not supported.\n";
}
}
static void convert(const std::vector<std::string>& args)
{
if (args.size() < 2 || args.size() > 4)
{
std::cerr << "Invalid arguments.\n";
std::cerr << "Usage: convert from_path to_path [append] [validate]\n";
return;
}
const bool append = std::find(args.begin() + 2, args.end(), "append") != args.end();
const bool validate = std::find(args.begin() + 2, args.end(), "validate") != args.end();
const std::ios_base::openmode openmode =
append
? std::ios_base::app
: std::ios_base::trunc;
convert(args[0], args[1], openmode, validate);
}
void convert(istringstream& is)
{
std::vector<std::string> args;
while (true)
{
std::string token = "";
is >> token;
if (token == "")
break;
args.push_back(token);
}
convert(args);
}
static void append_files_from_dir(
std::vector<std::string>& filenames,
const std::string& base_dir,
const std::string& target_dir)
{
string kif_base_dir = Path::combine(base_dir, target_dir);
sys::path p(kif_base_dir); // Origin of enumeration
std::for_each(sys::directory_iterator(p), sys::directory_iterator(),
[&](const sys::path& path) {
if (sys::is_regular_file(path))
filenames.push_back(Path::combine(target_dir, path.filename().generic_string()));
});
}
static void rebase_files(
std::vector<std::string>& filenames,
const std::string& base_dir)
{
for (auto& file : filenames)
{
file = Path::combine(base_dir, file);
}
}
void convert_bin_from_pgn_extract(std::istringstream& is)
{
std::vector<std::string> filenames;
string base_dir;
string target_dir;
bool pgn_eval_side_to_move = false;
bool convert_no_eval_fens_as_score_zero = false;
string output_file_name = "shuffled_sfen.bin";
while (true)
{
string option;
is >> option;
if (option == "")
break;
if (option == "targetdir") is >> target_dir;
else if (option == "targetfile")
{
std::string filename;
is >> filename;
filenames.push_back(filename);
}
else if (option == "basedir") is >> base_dir;
else if (option == "pgn_eval_side_to_move") is >> pgn_eval_side_to_move;
else if (option == "convert_no_eval_fens_as_score_zero") is >> convert_no_eval_fens_as_score_zero;
else if (option == "output_file_name") is >> output_file_name;
else
{
cout << "Unknown option: " << option << ". Ignoring.\n";
}
}
if (!target_dir.empty())
{
append_files_from_dir(filenames, base_dir, target_dir);
}
rebase_files(filenames, base_dir);
Eval::NNUE::init();
cout << "convert_bin_from_pgn-extract.." << endl;
convert_bin_from_pgn_extract(
filenames,
output_file_name,
pgn_eval_side_to_move,
convert_no_eval_fens_as_score_zero);
}
void convert_bin(std::istringstream& is)
{
std::vector<std::string> filenames;
string base_dir;
string target_dir;
int ply_minimum = 0;
int ply_maximum = 114514;
bool interpolate_eval = 0;
bool check_invalid_fen = false;
bool check_illegal_move = false;
bool pgn_eval_side_to_move = false;
bool convert_no_eval_fens_as_score_zero = false;
double src_score_min_value = 0.0;
double src_score_max_value = 1.0;
double dest_score_min_value = 0.0;
double dest_score_max_value = 1.0;
string output_file_name = "shuffled_sfen.bin";
while (true)
{
string option;
is >> option;
if (option == "")
break;
if (option == "targetdir") is >> target_dir;
else if (option == "targetfile")
{
std::string filename;
is >> filename;
filenames.push_back(filename);
}
else if (option == "basedir") is >> base_dir;
else if (option == "ply_minimum") is >> ply_minimum;
else if (option == "ply_maximum") is >> ply_maximum;
else if (option == "interpolate_eval") is >> interpolate_eval;
else if (option == "check_invalid_fen") is >> check_invalid_fen;
else if (option == "check_illegal_move") is >> check_illegal_move;
else if (option == "pgn_eval_side_to_move") is >> pgn_eval_side_to_move;
else if (option == "convert_no_eval_fens_as_score_zero") is >> convert_no_eval_fens_as_score_zero;
else if (option == "src_score_min_value") is >> src_score_min_value;
else if (option == "src_score_max_value") is >> src_score_max_value;
else if (option == "dest_score_min_value") is >> dest_score_min_value;
else if (option == "dest_score_max_value") is >> dest_score_max_value;
else if (option == "output_file_name") is >> output_file_name;
else
{
cout << "Unknown option: " << option << ". Ignoring.\n";
}
}
if (!target_dir.empty())
{
append_files_from_dir(filenames, base_dir, target_dir);
}
rebase_files(filenames, base_dir);
Eval::NNUE::init();
cout << "convert_bin.." << endl;
convert_bin(
filenames,
output_file_name,
ply_minimum,
ply_maximum,
interpolate_eval,
src_score_min_value,
src_score_max_value,
dest_score_min_value,
dest_score_max_value,
check_invalid_fen,
check_illegal_move
);
}
void convert_plain(std::istringstream& is)
{
std::vector<std::string> filenames;
string base_dir;
string target_dir;
string output_file_name = "shuffled_sfen.bin";
while (true)
{
string option;
is >> option;
if (option == "")
break;
if (option == "targetdir") is >> target_dir;
else if (option == "targetfile")
{
std::string filename;
is >> filename;
filenames.push_back(filename);
}
else if (option == "basedir") is >> base_dir;
else if (option == "output_file_name") is >> output_file_name;
else
{
cout << "Unknown option: " << option << ". Ignoring.\n";
}
}
if (!target_dir.empty())
{
append_files_from_dir(filenames, base_dir, target_dir);
}
rebase_files(filenames, base_dir);
Eval::NNUE::init();
cout << "convert_plain.." << endl;
convert_plain(filenames, output_file_name);
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef _CONVERT_H_
#define _CONVERT_H_
#include <vector>
#include <string>
#include <sstream>
namespace Stockfish::Tools {
void convert(std::istringstream& is);
void convert_bin_from_pgn_extract(std::istringstream& is);
void convert_bin(std::istringstream& is);
void convert_plain(std::istringstream& is);
}
#endif
+43
View File
@@ -0,0 +1,43 @@
#include "opening_book.h"
#include <fstream>
namespace Stockfish::Tools {
EpdOpeningBook::EpdOpeningBook(const std::string& file, PRNG& prng) :
OpeningBook(file)
{
std::ifstream in(file);
if (!in)
{
return;
}
std::string line;
while (std::getline(in, line))
{
if (line.empty())
continue;
fens.emplace_back(line);
}
Algo::shuffle(fens, prng);
}
static bool ends_with(const std::string& lhs, const std::string& end)
{
if (end.size() > lhs.size()) return false;
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
}
std::unique_ptr<OpeningBook> open_opening_book(const std::string& filename, PRNG& prng)
{
if (ends_with(filename, ".epd"))
return std::make_unique<EpdOpeningBook>(filename, prng);
return nullptr;
}
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef LEARN_OPENING_BOOK_H
#define LEARN_OPENING_BOOK_H
#include "misc.h"
#include "position.h"
#include "thread.h"
#include <vector>
#include <random>
#include <optional>
#include <string>
#include <cstdint>
#include <memory>
#include <mutex>
namespace Stockfish::Tools {
struct OpeningBook {
const std::string& next_fen()
{
assert(fens.size() > 0);
std::unique_lock lock(mutex);
auto& fen = fens[current_index++];
if (current_index >= fens.size())
current_index = 0;
return fen;
}
std::size_t size() const { return fens.size(); }
const std::string& get_filename() const { return filename; }
protected:
OpeningBook(const std::string& file) :
filename(file),
current_index(0)
{
}
std::mutex mutex;
std::string filename;
std::vector<std::string> fens;
std::size_t current_index;
};
struct EpdOpeningBook : OpeningBook {
EpdOpeningBook(const std::string& file, PRNG& prng);
};
std::unique_ptr<OpeningBook> open_opening_book(const std::string& filename, PRNG& prng);
}
#endif
+46
View File
@@ -0,0 +1,46 @@
#ifndef _PACKED_SFEN_H_
#define _PACKED_SFEN_H_
#include <vector>
#include <cstdint>
namespace Stockfish::Tools {
// packed sfen
struct PackedSfen { std::uint8_t data[32]; };
// Structure in which PackedSfen and evaluation value are integrated
// If you write different contents for each option, it will be a problem when reusing the teacher game
// For the time being, write all the following members regardless of the options.
struct PackedSfenValue
{
// phase
PackedSfen sfen;
// Evaluation value returned from Tools::search()
std::int16_t score;
// PV first move
// Used when finding the match rate with the teacher
std::uint16_t move;
// Trouble of the phase from the initial phase.
std::uint16_t gamePly;
// 1 if the player on this side ultimately wins the game. -1 if you are losing.
// 0 if a draw is reached.
// The draw is in the teacher position generation command gensfen,
// Only write if LEARN_GENSFEN_DRAW_RESULT is enabled.
std::int8_t game_result;
// When exchanging the file that wrote the teacher aspect with other people
//Because this structure size is not fixed, pad it so that it is 40 bytes in any environment.
std::uint8_t padding;
// 32 + 2 + 2 + 2 + 1 + 1 = 40bytes
};
// Phase array: PSVector stands for packed sfen vector.
using PSVector = std::vector<PackedSfenValue>;
}
#endif
+389
View File
@@ -0,0 +1,389 @@
#include "sfen_packer.h"
#include "packed_sfen.h"
#include "misc.h"
#include "position.h"
#include <sstream>
#include <fstream>
#include <cstring> // std::memset()
using namespace std;
namespace Stockfish::Tools {
// Class that handles bitstream
// useful when doing aspect encoding
struct BitStream
{
// Set the memory to store the data in advance.
// Assume that memory is cleared to 0.
void set_data(std::uint8_t* data_) { data = data_; reset(); }
// Get the pointer passed in set_data().
uint8_t* get_data() const { return data; }
// Get the cursor.
int get_cursor() const { return bit_cursor; }
// reset the cursor
void reset() { bit_cursor = 0; }
// Write 1bit to the stream.
// If b is non-zero, write out 1. If 0, write 0.
void write_one_bit(int b)
{
if (b)
data[bit_cursor / 8] |= 1 << (bit_cursor & 7);
++bit_cursor;
}
// Get 1 bit from the stream.
int read_one_bit()
{
int b = (data[bit_cursor / 8] >> (bit_cursor & 7)) & 1;
++bit_cursor;
return b;
}
// write n bits of data
// Data shall be written out from the lower order of d.
void write_n_bit(int d, int n)
{
for (int i = 0; i <n; ++i)
write_one_bit(d & (1 << i));
}
// read n bits of data
// Reverse conversion of write_n_bit().
int read_n_bit(int n)
{
int result = 0;
for (int i = 0; i < n; ++i)
result |= read_one_bit() ? (1 << i) : 0;
return result;
}
private:
// Next bit position to read/write.
int bit_cursor;
// data entity
std::uint8_t* data;
};
// Class for compressing/decompressing sfen
// sfen can be packed to 256bit (32bytes) by Huffman coding.
// This is proven by mini. The above is Huffman coding.
//
// Internal format = 1-bit turn + 7-bit king position *2 + piece on board (Huffman coding) + hand piece (Huffman coding)
// Side to move (White = 0, Black = 1) (1bit)
// White King Position (6 bits)
// Black King Position (6 bits)
// Huffman Encoding of the board
// Castling availability (1 bit x 4)
// En passant square (1 or 1 + 6 bits)
// Rule 50 (6 bits)
// Game play (8 bits)
//
// TODO(someone): Rename SFEN to FEN.
//
struct SfenPacker
{
void pack(const Position& pos, bool resetCastlingRights);
// sfen packed by pack() (256bit = 32bytes)
// Or sfen to decode with unpack()
uint8_t *data; // uint8_t[32];
BitStream stream;
// Output the board pieces to stream.
void write_board_piece_to_stream(Piece pc);
// Read one board piece from stream
Piece read_board_piece_from_stream();
};
// Huffman coding
// * is simplified from mini encoding to make conversion easier.
//
// Huffman Encoding
//
// Empty xxxxxxx0
// Pawn xxxxx001 + 1 bit (Color)
// Knight xxxxx011 + 1 bit (Color)
// Bishop xxxxx101 + 1 bit (Color)
// Rook xxxxx111 + 1 bit (Color)
// Queen xxxx1001 + 1 bit (Color)
//
// Worst case:
// - 32 empty squares 32 bits
// - 30 pieces 150 bits
// - 2 kings 12 bits
// - castling rights 4 bits
// - ep square 7 bits
// - rule50 7 bits
// - game ply 16 bits
// - TOTAL 228 bits < 256 bits
struct HuffmanedPiece
{
int code; // how it will be coded
int bits; // How many bits do you have
};
constexpr HuffmanedPiece huffman_table[] =
{
{0b0000,1}, // NO_PIECE
{0b0001,4}, // PAWN
{0b0011,4}, // KNIGHT
{0b0101,4}, // BISHOP
{0b0111,4}, // ROOK
{0b1001,4}, // QUEEN
};
// Pack sfen and store in data[32].
void SfenPacker::pack(const Position& pos, bool resetCastlingRights)
{
memset(data, 0, 32 /* 256bit */);
stream.set_data(data);
// turn
// Side to move.
stream.write_one_bit((int)(pos.side_to_move()));
// 7-bit positions for leading and trailing balls
// White king and black king, 6 bits for each.
for(auto c: Colors)
stream.write_n_bit(pos.king_square(c), 6);
// Write the pieces on the board other than the kings.
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
{
Piece pc = pos.piece_on(make_square(f, r));
if (type_of(pc) == KING)
continue;
write_board_piece_to_stream(pc);
}
}
if (resetCastlingRights)
{
stream.write_n_bit(0, 4);
}
else
{
stream.write_one_bit(pos.can_castle(WHITE_OO));
stream.write_one_bit(pos.can_castle(WHITE_OOO));
stream.write_one_bit(pos.can_castle(BLACK_OO));
stream.write_one_bit(pos.can_castle(BLACK_OOO));
}
if (pos.ep_square() == SQ_NONE) {
stream.write_one_bit(0);
}
else {
stream.write_one_bit(1);
stream.write_n_bit(static_cast<int>(pos.ep_square()), 6);
}
stream.write_n_bit(pos.state()->rule50, 6);
const int fm = 1 + (pos.game_ply()-(pos.side_to_move() == BLACK)) / 2;
stream.write_n_bit(fm, 8);
// Write high bits of half move. This is a fix for the
// limited range of half move counter.
// This is backwards compatibile.
stream.write_n_bit(fm >> 8, 8);
// Write the highest bit of rule50 at the end. This is a backwards
// compatibile fix for rule50 having only 6 bits stored.
// This bit is just ignored by the old parsers.
stream.write_n_bit(pos.state()->rule50 >> 6, 1);
assert(stream.get_cursor() <= 256);
}
// Output the board pieces to stream.
void SfenPacker::write_board_piece_to_stream(Piece pc)
{
// piece type
PieceType pr = type_of(pc);
auto c = huffman_table[pr];
stream.write_n_bit(c.code, c.bits);
if (pc == NO_PIECE)
return;
// first and second flag
stream.write_one_bit(color_of(pc));
}
// Read one board piece from stream
Piece SfenPacker::read_board_piece_from_stream()
{
PieceType pr = NO_PIECE_TYPE;
int code = 0, bits = 0;
while (true)
{
code |= stream.read_one_bit() << bits;
++bits;
assert(bits <= 6);
for (pr = NO_PIECE_TYPE; pr <KING; ++pr)
if (huffman_table[pr].code == code
&& huffman_table[pr].bits == bits)
goto Found;
}
Found:;
if (pr == NO_PIECE_TYPE)
return NO_PIECE;
// first and second flag
Color c = (Color)stream.read_one_bit();
return make_piece(c, pr);
}
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool frc)
{
SfenPacker packer;
auto& stream = packer.stream;
// TODO: separate streams for writing and reading. Here we actually have to
// const_cast which is not safe in the long run.
stream.set_data(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&sfen)));
pos.clear();
std::memset(si, 0, sizeof(StateInfo));
si->accumulator.computed[WHITE] = false;
si->accumulator.computed[BLACK] = false;
pos.st = si;
// Active color
pos.sideToMove = (Color)stream.read_one_bit();
// First the position of the ball
for (auto c : Colors)
pos.board[stream.read_n_bit(6)] = make_piece(c, KING);
// Piece placement
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
{
auto sq = make_square(f, r);
// it seems there are already balls
Piece pc;
if (type_of(pos.board[sq]) != KING)
{
assert(pos.board[sq] == NO_PIECE);
pc = packer.read_board_piece_from_stream();
}
else
{
pc = pos.board[sq];
// put_piece() will catch ASSERT unless you remove it all.
pos.board[sq] = NO_PIECE;
}
// There may be no pieces, so skip in that case.
if (pc == NO_PIECE)
continue;
pos.put_piece(Piece(pc), sq);
if (stream.get_cursor()> 256)
return 1;
}
}
// Castling availability.
pos.st->castlingRights = 0;
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(WHITE, SQ_H1); pos.piece_on(rsq) != W_ROOK; --rsq) {}
pos.set_castling_right(WHITE, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(WHITE, SQ_A1); pos.piece_on(rsq) != W_ROOK; ++rsq) {}
pos.set_castling_right(WHITE, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(BLACK, SQ_H1); pos.piece_on(rsq) != B_ROOK; --rsq) {}
pos.set_castling_right(BLACK, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(BLACK, SQ_A1); pos.piece_on(rsq) != B_ROOK; ++rsq) {}
pos.set_castling_right(BLACK, rsq);
}
// En passant square. Ignore if no pawn capture is possible
if (stream.read_one_bit()) {
Square ep_square = static_cast<Square>(stream.read_n_bit(6));
pos.st->epSquare = ep_square;
if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN))
|| !(pos.pieces(~pos.sideToMove, PAWN) & (pos.st->epSquare + pawn_push(~pos.sideToMove))))
pos.st->epSquare = SQ_NONE;
}
else {
pos.st->epSquare = SQ_NONE;
}
// Halfmove clock
pos.st->rule50 = stream.read_n_bit(6);
// Fullmove number
pos.gamePly = stream.read_n_bit(8);
// Read the highest bit of rule50. This was added as a fix for rule50
// counter having only 6 bits stored.
// In older entries this will just be a zero bit.
pos.gamePly |= stream.read_n_bit(8) << 8;
// Read the highest bit of rule50. This was added as a fix for rule50
// counter having only 6 bits stored.
// In older entries this will just be a zero bit.
pos.st->rule50 |= stream.read_n_bit(1) << 6;
// Convert from fullmove starting from 1 to gamePly starting from 0,
// handle also common incorrect FEN with fullmove = 0.
pos.gamePly = std::max(2 * (pos.gamePly - 1), 0) + (pos.sideToMove == BLACK);
assert(stream.get_cursor() <= 256);
pos.chess960 = frc;
pos.thisThread = th;
pos.set_state(pos.st);
assert(pos.pos_is_ok());
return 0;
}
PackedSfen sfen_pack(Position& pos, bool resetCastlingRights)
{
PackedSfen sfen;
SfenPacker sp;
sp.data = (uint8_t*)&sfen;
sp.pack(pos, resetCastlingRights);
return sfen;
}
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef _SFEN_PACKER_H_
#define _SFEN_PACKER_H_
#include "types.h"
#include "packed_sfen.h"
#include <cstdint>
namespace Stockfish {
class Position;
struct StateInfo;
class Thread;
}
namespace Stockfish::Tools {
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool frc);
PackedSfen sfen_pack(Position& pos, bool resetCastlingRights);
}
#endif
+352
View File
@@ -0,0 +1,352 @@
#include "sfen_stream.h"
#include "packed_sfen.h"
#include "misc.h"
#include <string>
#include <vector>
#include <deque>
#include <memory>
#include <mutex>
#include <list>
#include <atomic>
#include <optional>
#include <iostream>
#include <cstdint>
#include <thread>
#include <functional>
namespace Stockfish::Tools{
enum struct SfenReaderMode
{
Sequential,
Cyclic
};
// Sfen reader
struct SfenReader
{
// Number of phases buffered by each thread 0.1M phases. 4M phase at 40HT
static constexpr size_t DEFAULT_THREAD_BUFFER_SIZE = 10 * 1000;
// Buffer for reading files (If this is made larger,
// the shuffle becomes larger and the phases may vary.
// If it is too large, the memory consumption will increase.
// SFEN_READ_SIZE is a multiple of THREAD_BUFFER_SIZE.
static constexpr const size_t DEFAULT_SFEN_READ_SIZE = 1000 * 1000 * 10;
// Do not use std::random_device().
// Because it always the same integers on MinGW.
SfenReader(
const std::vector<std::string>& filenames_,
bool do_shuffle,
SfenReaderMode mode_,
int thread_num,
const std::string& seed,
size_t read_size = DEFAULT_SFEN_READ_SIZE,
size_t buffer_size = DEFAULT_THREAD_BUFFER_SIZE
) :
filenames(filenames_.begin(), filenames_.end()),
mode(mode_),
// Due to the implementation of waiting for buffer empty a bit
// the read size must be at least twice the buffer size.
sfen_read_size(std::max(read_size, buffer_size * 2)),
thread_buffer_size(buffer_size),
prng(seed)
{
packed_sfens.resize(thread_num);
total_read = 0;
end_of_files = false;
shuffle = do_shuffle;
stop_flag = false;
num_buffers_in_pool.store(0);
file_worker_thread = std::thread([&] {
this->file_read_worker();
});
}
~SfenReader()
{
stop_flag = true;
if (file_worker_thread.joinable())
file_worker_thread.join();
}
// Load the phase for calculation such as mse.
PSVector read_some(uint64_t count, uint64_t count_tries, std::function<bool(const PackedSfenValue&)> do_take)
{
PSVector psv;
psv.reserve(count);
for (uint64_t i = 0; i < count_tries; ++i)
{
PackedSfenValue ps;
if (!read_to_thread_buffer(0, ps))
{
std::cout << "ERROR (sfen_reader): Reading failed." << std::endl;
return psv;
}
if (do_take(ps))
{
psv.push_back(ps);
if (psv.size() >= count)
break;
}
}
return psv;
}
// [ASYNC] Thread returns one aspect. Otherwise returns false.
bool read_to_thread_buffer(size_t thread_id, PackedSfenValue& ps)
{
// If there are any positions left in the thread buffer
// then retrieve one and return it.
auto& thread_ps = packed_sfens[thread_id];
// Fill the read buffer if there is no remaining buffer,
// but if it doesn't even exist, finish.
// If the buffer is empty, fill it.
if ((thread_ps == nullptr || thread_ps->empty())
&& !read_to_thread_buffer_impl(thread_id))
return false;
// read_to_thread_buffer_impl() returned true,
// Since the filling of the thread buffer with the
// phase has been completed successfully
// thread_ps->rbegin() is alive.
ps = thread_ps->back();
thread_ps->pop_back();
// If you've run out of buffers, call delete yourself to free this buffer.
if (thread_ps->empty())
{
thread_ps.reset();
}
return true;
}
// [ASYNC] Read some aspects into thread buffer.
bool read_to_thread_buffer_impl(size_t thread_id)
{
while (true)
{
{
std::unique_lock<std::mutex> lk(mutex);
// If you can fill from the file buffer, that's fine.
if (packed_sfens_pool.size() != 0)
{
// It seems that filling is possible, so fill and finish.
packed_sfens[thread_id] = std::move(packed_sfens_pool.front());
packed_sfens_pool.pop_front();
num_buffers_in_pool.fetch_sub(1);
total_read += thread_buffer_size;
return true;
}
}
// The file to read is already gone. No more use.
if (end_of_files)
return false;
// Waiting for file worker to fill packed_sfens_pool.
// The mutex isn't locked, so it should fill up soon.
// Poor man's condition variable.
sleep(1);
}
}
void file_read_worker()
{
std::string currentFilename;
uint64_t numEntriesReadFromCurrentFile = 0;
auto open_next_file = [&]() {
// no more
for(;;)
{
sfen_input_stream.reset();
if (filenames.empty())
return false;
// Get the next file name.
currentFilename = filenames.front();
filenames.pop_front();
numEntriesReadFromCurrentFile = 0;
sfen_input_stream = open_sfen_input_file(currentFilename);
auto out = sync_region_cout.new_region();
if (sfen_input_stream == nullptr)
{
out << "INFO (sfen_reader): File does not exist: " << currentFilename << '\n';
}
else
{
out << "INFO (sfen_reader): Opened file for reading: " << currentFilename << '\n';
// in case the file is empty or was deleted.
if (sfen_input_stream->eof())
{
out << " - File empty, nothing to read.\n";
}
else
{
return true;
}
}
}
};
if (sfen_input_stream == nullptr && !open_next_file())
{
auto out = sync_region_cout.new_region();
out << "INFO (sfen_reader): End of files." << std::endl;
end_of_files = true;
return;
}
// We want to set the `end_of_files` only after we read everything AND copy to the buffer pool.
bool local_end_of_files = false;
while (!local_end_of_files)
{
// Wait for the buffer to run out.
// This size() is read only, so you don't need to lock it.
while (!stop_flag && num_buffers_in_pool.load() >= sfen_read_size / thread_buffer_size)
sleep(100);
if (stop_flag)
return;
PSVector sfens;
sfens.reserve(sfen_read_size);
// Read from the file into the file buffer.
while (sfens.size() < sfen_read_size)
{
std::optional<PackedSfenValue> p = sfen_input_stream->next();
if (p.has_value())
{
sfens.push_back(*p);
++numEntriesReadFromCurrentFile;
}
else
{
if (mode == SfenReaderMode::Cyclic
&& numEntriesReadFromCurrentFile > 0)
{
// The file contained data so we add it again to the end of the queue.
filenames.emplace_back(currentFilename);
}
if(!open_next_file())
{
// There was no next file. Abort.
auto out = sync_region_cout.new_region();
out << "INFO (sfen_reader): End of files." << std::endl;
local_end_of_files = true;
break;
}
}
}
// Shuffle the read phase data.
if (shuffle)
{
Algo::shuffle(sfens, prng);
}
std::vector<std::unique_ptr<PSVector>> buffers;
for (size_t offset = 0; offset < sfens.size(); offset += thread_buffer_size)
{
const size_t count =
offset + thread_buffer_size > sfens.size()
? sfens.size() - offset
: thread_buffer_size;
// Delete this pointer on the receiving side.
auto buf = std::make_unique<PSVector>();
buf->resize(count);
memcpy(
buf->data(),
&sfens[offset],
sizeof(PackedSfenValue) * count);
buffers.emplace_back(std::move(buf));
}
{
std::unique_lock<std::mutex> lk(mutex);
// The mutex lock is required because the%
// contents of packed_sfens_pool are changed.
for (auto& buf : buffers)
{
num_buffers_in_pool.fetch_add(1);
packed_sfens_pool.emplace_back(std::move(buf));
}
}
}
end_of_files = true;
}
protected:
// worker thread reading file in background
std::thread file_worker_thread;
// sfen files
std::deque<std::string> filenames;
std::atomic<bool> stop_flag;
// number of phases read (file to memory buffer)
std::atomic<uint64_t> total_read;
// Do not shuffle when reading the phase.
bool shuffle;
SfenReaderMode mode;
size_t sfen_read_size;
size_t thread_buffer_size;
// Random number to shuffle when reading the phase
PRNG prng;
// Did you read the files and reached the end?
std::atomic<bool> end_of_files;
// handle of sfen file
std::unique_ptr<BasicSfenInputStream> sfen_input_stream;
// sfen for each thread
// (When the thread is used up, the thread should call delete to release it.)
std::vector<std::unique_ptr<PSVector>> packed_sfens;
// Mutex when accessing packed_sfens_pool
std::mutex mutex;
// pool of sfen. The worker thread read from the file is added here.
// Each worker thread fills its own packed_sfens[thread_id] from here.
// * Lock and access the mutex.
std::list<std::unique_ptr<PSVector>> packed_sfens_pool;
std::atomic<size_t> num_buffers_in_pool;
};
}
+222
View File
@@ -0,0 +1,222 @@
#ifndef _SFEN_STREAM_H_
#define _SFEN_STREAM_H_
#include "packed_sfen.h"
#include "extra/nnue_data_binpack_format.h"
#include <optional>
#include <fstream>
#include <string>
#include <memory>
namespace Stockfish::Tools {
enum struct SfenOutputType
{
Bin,
Binpack
};
static bool ends_with(const std::string& lhs, const std::string& end)
{
if (end.size() > lhs.size()) return false;
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
}
static bool has_extension(const std::string& filename, const std::string& extension)
{
return ends_with(filename, "." + extension);
}
static std::string filename_with_extension(const std::string& filename, const std::string& ext)
{
if (ends_with(filename, ext))
{
return filename;
}
else
{
return filename + "." + ext;
}
}
struct BasicSfenInputStream
{
virtual std::optional<PackedSfenValue> next() = 0;
virtual bool eof() const = 0;
virtual ~BasicSfenInputStream() {}
};
struct BinSfenInputStream : BasicSfenInputStream
{
static constexpr auto openmode = std::ios::in | std::ios::binary;
static inline const std::string extension = "bin";
BinSfenInputStream(std::string filename) :
m_stream(filename, openmode),
m_eof(!m_stream)
{
}
std::optional<PackedSfenValue> next() override
{
PackedSfenValue e;
if(m_stream.read(reinterpret_cast<char*>(&e), sizeof(PackedSfenValue)))
{
return e;
}
else
{
m_eof = true;
return std::nullopt;
}
}
bool eof() const override
{
return m_eof;
}
~BinSfenInputStream() override {}
private:
std::fstream m_stream;
bool m_eof;
};
struct BinpackSfenInputStream : BasicSfenInputStream
{
static constexpr auto openmode = std::ios::in | std::ios::binary;
static inline const std::string extension = "binpack";
BinpackSfenInputStream(std::string filename) :
m_stream(filename, openmode),
m_eof(!m_stream.hasNext())
{
}
std::optional<PackedSfenValue> next() override
{
static_assert(sizeof(binpack::nodchip::PackedSfenValue) == sizeof(PackedSfenValue));
if (!m_stream.hasNext())
{
m_eof = true;
return std::nullopt;
}
auto training_data_entry = m_stream.next();
auto v = binpack::trainingDataEntryToPackedSfenValue(training_data_entry);
PackedSfenValue psv;
// same layout, different types. One is from generic library.
std::memcpy(&psv, &v, sizeof(PackedSfenValue));
return psv;
}
bool eof() const override
{
return m_eof;
}
~BinpackSfenInputStream() override {}
private:
binpack::CompressedTrainingDataEntryReader m_stream;
bool m_eof;
};
struct BasicSfenOutputStream
{
virtual void write(const PSVector& sfens) = 0;
virtual ~BasicSfenOutputStream() {}
};
struct BinSfenOutputStream : BasicSfenOutputStream
{
static constexpr auto openmode = std::ios::out | std::ios::binary | std::ios::app;
static inline const std::string extension = "bin";
BinSfenOutputStream(std::string filename) :
m_stream(filename_with_extension(filename, extension), openmode)
{
}
void write(const PSVector& sfens) override
{
m_stream.write(reinterpret_cast<const char*>(sfens.data()), sizeof(PackedSfenValue) * sfens.size());
}
~BinSfenOutputStream() override {}
private:
std::fstream m_stream;
};
struct BinpackSfenOutputStream : BasicSfenOutputStream
{
static constexpr auto openmode = std::ios::out | std::ios::binary | std::ios::app;
static inline const std::string extension = "binpack";
BinpackSfenOutputStream(std::string filename) :
m_stream(filename_with_extension(filename, extension), openmode)
{
}
void write(const PSVector& sfens) override
{
static_assert(sizeof(binpack::nodchip::PackedSfenValue) == sizeof(PackedSfenValue));
for(auto& sfen : sfens)
{
// The library uses a type that's different but layout-compatibile.
binpack::nodchip::PackedSfenValue e;
std::memcpy(&e, &sfen, sizeof(binpack::nodchip::PackedSfenValue));
m_stream.addTrainingDataEntry(binpack::packedSfenValueToTrainingDataEntry(e));
}
}
~BinpackSfenOutputStream() override {}
private:
binpack::CompressedTrainingDataEntryWriter m_stream;
};
inline std::unique_ptr<BasicSfenInputStream> open_sfen_input_file(const std::string& filename)
{
if (has_extension(filename, BinSfenInputStream::extension))
return std::make_unique<BinSfenInputStream>(filename);
else if (has_extension(filename, BinpackSfenInputStream::extension))
return std::make_unique<BinpackSfenInputStream>(filename);
return nullptr;
}
inline std::unique_ptr<BasicSfenOutputStream> create_new_sfen_output(const std::string& filename, SfenOutputType sfen_output_type)
{
switch(sfen_output_type)
{
case SfenOutputType::Bin:
return std::make_unique<BinSfenOutputStream>(filename);
case SfenOutputType::Binpack:
return std::make_unique<BinpackSfenOutputStream>(filename);
}
assert(false);
return nullptr;
}
inline std::unique_ptr<BasicSfenOutputStream> create_new_sfen_output(const std::string& filename)
{
if (has_extension(filename, BinSfenOutputStream::extension))
return std::make_unique<BinSfenOutputStream>(filename);
else if (has_extension(filename, BinpackSfenOutputStream::extension))
return std::make_unique<BinpackSfenOutputStream>(filename);
return nullptr;
}
}
#endif
+203
View File
@@ -0,0 +1,203 @@
#include "packed_sfen.h"
#include "sfen_stream.h"
#include "misc.h"
#include "extra/nnue_data_binpack_format.h"
#include "syzygy/tbprobe.h"
#include <cstring>
#include <fstream>
#include <limits>
#include <list>
#include <memory>
#include <optional>
#include <shared_mutex>
#include <thread>
#include <atomic>
namespace Stockfish::Tools {
// Helper class for exporting Sfen
struct SfenWriter
{
// Amount of sfens required to flush the buffer.
static constexpr size_t SFEN_WRITE_SIZE = 5000;
// File name to write and number of threads to create
SfenWriter(std::string filename_, int thread_num, uint64_t save_count, SfenOutputType sfen_output_type)
{
sfen_buffers_pool.reserve((size_t)thread_num * 10);
sfen_buffers.resize(thread_num);
auto out = sync_region_cout.new_region();
out << "INFO (sfen_writer): Creating new data file at " << filename_ << std::endl;
sfen_format = sfen_output_type;
output_file_stream = create_new_sfen_output(filename_, sfen_format);
filename = filename_;
save_every = save_count;
finished = false;
file_worker_thread = std::thread([&] { this->file_write_worker(); });
}
~SfenWriter()
{
flush();
finished = true;
file_worker_thread.join();
output_file_stream.reset();
#if !defined(NDEBUG)
{
// All buffers should be empty since file_worker_thread
// should have written everything before exiting.
for (const auto& p : sfen_buffers) { assert(p == nullptr); (void)p ; }
assert(sfen_buffers_pool.empty());
}
#endif
}
void write(size_t thread_id, const PackedSfenValue& psv)
{
// We have a buffer for each thread and add it there.
// If the buffer overflows, write it to a file.
// This buffer is prepared for each thread.
auto& buf = sfen_buffers[thread_id];
// Secure since there is no buf at the first time
// and immediately after writing the thread buffer.
if (!buf)
{
buf = std::make_unique<PSVector>();
buf->reserve(SFEN_WRITE_SIZE);
}
// Buffer is exclusive to this thread.
// There is no need for a critical section.
buf->push_back(psv);
if (buf->size() >= SFEN_WRITE_SIZE)
{
// If you load it in sfen_buffers_pool, the worker will do the rest.
// Critical section since sfen_buffers_pool is shared among threads.
std::unique_lock<std::mutex> lk(mutex);
sfen_buffers_pool.emplace_back(std::move(buf));
}
}
void flush()
{
for (size_t i = 0; i < sfen_buffers.size(); ++i)
{
flush(i);
}
}
// Move what remains in the buffer for your thread to a buffer for writing to a file.
void flush(size_t thread_id)
{
std::unique_lock<std::mutex> lk(mutex);
auto& buf = sfen_buffers[thread_id];
// There is a case that buf==nullptr, so that check is necessary.
if (buf && buf->size() != 0)
{
sfen_buffers_pool.emplace_back(std::move(buf));
}
}
// Dedicated thread to write to file
void file_write_worker()
{
while (!finished || sfen_buffers_pool.size())
{
std::vector<std::unique_ptr<PSVector>> buffers;
{
std::unique_lock<std::mutex> lk(mutex);
// Atomically swap take the filled buffers and
// create a new buffer pool for threads to fill.
buffers = std::move(sfen_buffers_pool);
sfen_buffers_pool = std::vector<std::unique_ptr<PSVector>>();
}
if (!buffers.size())
{
// Poor man's condition variable.
sleep(100);
}
else
{
for (auto& buf : buffers)
{
output_file_stream->write(*buf);
sfen_write_count += buf->size();
// Add the processed number here, and if it exceeds save_every,
// change the file name and reset this counter.
sfen_write_count_current_file += buf->size();
if (sfen_write_count_current_file >= save_every)
{
sfen_write_count_current_file = 0;
// Sequential number attached to the file
int n = (int)(sfen_write_count / save_every);
// Rename the file and open it again.
// Add ios::app in consideration of overwriting.
// (Depending on the operation, it may not be necessary.)
std::string new_filename = filename + "_" + std::to_string(n);
output_file_stream = create_new_sfen_output(new_filename, sfen_format);
auto out = sync_region_cout.new_region();
out << "INFO (sfen_writer): Creating new data file at " << new_filename << std::endl;
}
}
}
}
}
private:
std::unique_ptr<BasicSfenOutputStream> output_file_stream;
// A new net is saved after every save_every sfens are processed.
uint64_t save_every = std::numeric_limits<uint64_t>::max();
// File name passed in the constructor
std::string filename;
// Thread to write to the file
std::thread file_worker_thread;
// Flag that all threads have finished
std::atomic<bool> finished;
SfenOutputType sfen_format;
// buffer before writing to file
// sfen_buffers is the buffer for each thread
// sfen_buffers_pool is a buffer for writing.
// After loading the phase in the former buffer by SFEN_WRITE_SIZE,
// transfer it to the latter.
std::vector<std::unique_ptr<PSVector>> sfen_buffers;
std::vector<std::unique_ptr<PSVector>> sfen_buffers_pool;
// Mutex required to access sfen_buffers_pool
std::mutex mutex;
// Number of sfens written in total, and the
// number of sfens written in the current file.
uint64_t sfen_write_count = 0;
uint64_t sfen_write_count_current_file = 0;
};
}
+1278
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
#ifndef _STATS_H_
#define _STATS_H_
#include <sstream>
namespace Stockfish::Tools::Stats {
void gather_statistics(std::istringstream& is);
}
#endif
+952
View File
@@ -0,0 +1,952 @@
#include "training_data_generator.h"
#include "sfen_writer.h"
#include "packed_sfen.h"
#include "opening_book.h"
#include "misc.h"
#include "position.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue.h"
#include "syzygy/tbprobe.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <limits>
#include <list>
#include <memory>
#include <optional>
#include <random>
#include <shared_mutex>
#include <sstream>
#include <unordered_set>
using namespace std;
namespace Stockfish::Tools
{
// Class to generate sfen with multiple threads
struct TrainingDataGenerator
{
struct Params
{
// Min and max depths for search during gensfen
int search_depth_min = 3;
int search_depth_max = -1;
// Number of the nodes to be searched.
// 0 represents no limits.
uint64_t nodes = 0;
// Upper limit of evaluation value of generated situation
int eval_limit = 3000;
// minimum ply with random move
// maximum ply with random move
// Number of random moves in one station
int random_move_minply = 1;
int random_move_maxply = 24;
int random_move_count = 5;
// Move kings with a probability of 1/N when randomly moving like Apery software.
// When you move the king again, there is a 1/N chance that it will randomly moved
// once in the opponent's turn.
// Apery has N=2. Specifying 0 here disables this function.
int random_move_like_apery = 0;
// For when using multi pv instead of random move.
// random_multi_pv is the number of candidates for MultiPV.
// When adopting the move of the candidate move, the difference
// between the evaluation value of the move of the 1st place
// and the evaluation value of the move of the Nth place is.
// Must be in the range random_multi_pv_diff.
// random_multi_pv_depth is the search depth for MultiPV.
int random_multi_pv = 0;
int random_multi_pv_diff = 32000;
int random_multi_pv_depth = -1;
uint64_t random_multi_pv_nodes = 0;
// The minimum and maximum ply (number of steps from
// the initial phase) of the sfens to write out.
int write_minply = 16;
int write_maxply = 400;
uint64_t save_every = std::numeric_limits<uint64_t>::max();
std::string output_file_name = "training_data";
SfenOutputType sfen_format = SfenOutputType::Binpack;
std::string seed;
bool write_out_draw_game_in_training_data_generation = true;
bool detect_draw_by_consecutive_low_score = true;
bool detect_draw_by_insufficient_mating_material = true;
uint64_t num_threads;
std::string book;
void enforce_constraints()
{
search_depth_max = std::max(search_depth_min, search_depth_max);
// Limit the maximum to a one-stop score. (Otherwise you might not end the loop)
eval_limit = std::min(eval_limit, (int)mate_in(2));
save_every = std::max(save_every, REPORT_STATS_EVERY);
num_threads = Options["Threads"];
if (random_multi_pv_depth == -1)
random_multi_pv_depth = search_depth_max;
if (random_multi_pv_nodes == 0)
random_multi_pv_nodes = nodes;
}
};
// Hash to limit the export of identical sfens
static constexpr uint64_t GENSFEN_HASH_SIZE = 64 * 1024 * 1024;
// It must be 2**N because it will be used as the mask to calculate hash_index.
static_assert((GENSFEN_HASH_SIZE& (GENSFEN_HASH_SIZE - 1)) == 0);
static constexpr uint64_t REPORT_DOT_EVERY = 5000;
static constexpr uint64_t REPORT_STATS_EVERY = 200000;
static_assert(REPORT_STATS_EVERY % REPORT_DOT_EVERY == 0);
TrainingDataGenerator(
const Params& prm
) :
params(prm),
sfen_writer(prm.output_file_name, prm.num_threads, prm.save_every, prm.sfen_format)
{
hash.resize(GENSFEN_HASH_SIZE);
prngs.reserve(prm.num_threads);
auto seed = prm.seed;
for (uint64_t i = 0; i < prm.num_threads; ++i)
{
prngs.emplace_back(seed);
seed = prngs.back().next_random_seed();
}
if (!prm.book.empty())
{
opening_book = open_opening_book(prm.book, prngs[0]);
if (opening_book == nullptr)
{
std::cout << "WARNING: Failed to open opening book " << prm.book << ". Falling back to startpos.\n";
}
}
// Output seed to veryfy by the user if it's not identical by chance.
std::cout << prngs[0] << std::endl;
}
void generate(uint64_t limit, uint64_t limit_seconds);
private:
Params params;
std::vector<PRNG> prngs;
std::mutex stats_mutex;
TimePoint last_stats_report_time;
// sfen exporter
SfenWriter sfen_writer;
SynchronizedRegionLogger::Region out;
vector<Key> hash; // 64MB*sizeof(HASH_KEY) = 512MB
std::unique_ptr<OpeningBook> opening_book;
static void set_gensfen_search_limits();
void generate_worker(
Thread& th,
std::atomic<uint64_t>& counter,
uint64_t limit,
uint64_t limit_seconds);
bool was_seen_before(const Position& pos);
optional<int8_t> get_current_game_result(
Position& pos,
const vector<int>& move_hist_scores) const;
vector<uint8_t> generate_random_move_flags(PRNG& prng);
optional<Move> choose_random_move(
PRNG& prng,
Position& pos,
std::vector<uint8_t>& random_move_flag,
int ply,
int& random_move_c);
bool commit_psv(
Thread& th,
PSVector& sfens,
int8_t lastTurnIsWin,
std::atomic<uint64_t>& counter,
uint64_t limit,
Color result_color);
void initial_report();
void report(uint64_t done, uint64_t new_done);
void maybe_report(uint64_t done);
};
void TrainingDataGenerator::set_gensfen_search_limits()
{
// About Search::Limits
// Be careful because this member variable is global and affects other threads.
auto& limits = Search::Limits;
// Make the search equivalent to the "go infinite" command. (Because it is troublesome if time management is done)
limits.infinite = true;
// Since PV is an obstacle when displayed, erase it.
limits.silent = true;
// If you use this, it will be compared with the accumulated nodes of each thread. Therefore, do not use it.
limits.nodes = 0;
// depth is also processed by the one passed as an argument of Tools::search().
limits.depth = 0;
}
void TrainingDataGenerator::generate(uint64_t limit, uint64_t limit_seconds)
{
last_stats_report_time = 0;
initial_report();
set_gensfen_search_limits();
std::atomic<uint64_t> counter{0};
Threads.execute_with_workers([&counter, limit, limit_seconds, this](Thread& th) {
generate_worker(th, counter, limit, limit_seconds);
});
Threads.wait_for_workers_finished();
sfen_writer.flush();
report(counter.load(), counter.load() % REPORT_STATS_EVERY + 1);
std::cout << std::endl;
}
void TrainingDataGenerator::generate_worker(
Thread& th,
std::atomic<uint64_t>& counter,
uint64_t limit,
uint64_t limit_seconds)
{
// For the time being, it will be treated as a draw
// at the maximum number of steps to write.
// Maximum StateInfo + Search PV to advance to leaf buffer
std::vector<StateInfo, AlignedAllocator<StateInfo>> states(
params.write_maxply + MAX_PLY /* == search_depth_min + α */);
StateInfo si;
auto& prng = prngs[th.id()];
// end flag
bool quit = false;
const auto start_time = now();
const bool frc = Options["UCI_Chess960"];
// repeat until the specified number of times
while (!quit)
{
if (static_cast<uint64_t>(now() - start_time) / 1000 >= limit_seconds)
{
break;
}
// It is necessary to set a dependent thread for Position.
// When parallelizing, Threads (since this is a vector<Thread*>,
// Do the same for up to Threads[0]...Threads[thread_num-1].
auto& pos = th.rootPos;
if (opening_book != nullptr)
{
auto& fen = opening_book->next_fen();
pos.set(fen, frc, &si, &th);
}
else
{
pos.set(StartFEN, frc, &si, &th);
}
int resign_counter = 0;
bool should_resign = prng.rand(10) > 1;
// Vector for holding the sfens in the current simulated game.
PSVector packed_sfens;
packed_sfens.reserve(params.write_maxply + MAX_PLY);
// Precomputed flags. Used internally by choose_random_move.
vector<uint8_t> random_move_flag = generate_random_move_flags(prng);
// A counter that keeps track of the number of random moves
// When random_move_minply == -1, random moves are
// performed continuously, so use it at this time.
// Used internally by choose_random_move.
int actual_random_move_count = 0;
// Save history of move scores for adjudication
vector<int> move_hist_scores;
auto flush_psv = [&](int8_t result) {
quit = commit_psv(th, packed_sfens, result, counter, limit, pos.side_to_move());
};
for (int ply = 0; ; ++ply)
{
// Current search depth
const int depth = params.search_depth_min + (int)prng.rand(params.search_depth_max - params.search_depth_min + 1);
// Starting search calls init_for_search
auto [search_value, search_pv] = Search::search(pos, depth, 1, params.nodes);
// This has to be performed after search because it needs to know
// rootMoves which are filled in init_for_search.
const auto result = get_current_game_result(pos, move_hist_scores);
if (result.has_value())
{
flush_psv(result.value());
break;
}
// Always adjudivate by eval limit.
// Also because of this we don't have to check for TB/MATE scores
if (abs(search_value) >= params.eval_limit)
{
resign_counter++;
if ((should_resign && resign_counter >= 4) || abs(search_value) >= VALUE_KNOWN_WIN) {
flush_psv((search_value >= params.eval_limit) ? 1 : -1);
break;
}
}
else
{
resign_counter = 0;
}
// In case there is no PV and the game was not ended here
// there is nothing we can do, we can't continue the game,
// we don't know the result, so discard this game.
if (search_pv.empty())
{
break;
}
// Save the move score for adjudication.
move_hist_scores.push_back(search_value);
// Discard stuff before write_minply is reached
// because it can harm training due to overfitting.
// Initial positions would be too common.
if (ply >= params.write_minply && !was_seen_before(pos))
{
auto& psv = packed_sfens.emplace_back();
// Here we only write the position data.
// Result is added after the whole game is done.
pos.sfen_pack(psv.sfen, pos.is_chess960());
psv.score = search_value;
psv.move = search_pv[0];
psv.gamePly = ply;
}
// Update the next move according to best search result or random move.
auto random_move = choose_random_move(prng, pos, random_move_flag, ply, actual_random_move_count);
const Move next_move = random_move.has_value() ? *random_move : search_pv[0];
// We don't have the whole game yet, but it ended,
// so the writing process ends and the next game starts.
// This shouldn't really happen.
if (!is_ok(next_move))
{
break;
}
// Do move.
pos.do_move(next_move, states[ply]);
}
}
}
bool TrainingDataGenerator::was_seen_before(const Position& pos)
{
// Look into the position hashtable to see if the same
// position was seen before.
// This is a good heuristic to exlude already seen
// positions without many false positives.
auto key = pos.key();
auto hash_index = (size_t)(key & (GENSFEN_HASH_SIZE - 1));
auto old_key = hash[hash_index];
if (key == old_key)
{
return true;
}
else
{
// Replace with the current key.
hash[hash_index] = key;
return false;
}
}
optional<int8_t> TrainingDataGenerator::get_current_game_result(
Position& pos,
const vector<int>& move_hist_scores) const
{
// Variables for draw adjudication.
// Todo: Make this as an option.
// start the adjudication when ply reaches this value
constexpr int adj_draw_ply = 80;
// 4 move scores for each side have to be checked
constexpr int adj_draw_cnt = 8;
// move score in CP
constexpr int adj_draw_score = 0;
// For the time being, it will be treated as a
// draw at the maximum number of steps to write.
const int ply = move_hist_scores.size();
// has it reached the max length or is a draw by fifty-move rule
// or by 3-fold repetition
if (ply >= params.write_maxply
|| pos.is_fifty_move_draw()
|| pos.is_three_fold_repetition())
{
return 0;
}
if(pos.this_thread()->rootMoves.empty())
{
// If there is no legal move
return pos.checkers()
? -1 /* mate */
: 0 /* stalemate */;
}
// Adjudicate game to a draw if the last 4 scores of each engine is 0.
if (params.detect_draw_by_consecutive_low_score)
{
if (ply >= adj_draw_ply)
{
int num_cons_plies_within_draw_score = 0;
bool is_adj_draw = false;
for (auto it = move_hist_scores.rbegin();
it != move_hist_scores.rend(); ++it)
{
if (abs(*it) <= adj_draw_score)
{
num_cons_plies_within_draw_score++;
}
else
{
// Draw scores must happen on consecutive plies
break;
}
if (num_cons_plies_within_draw_score >= adj_draw_cnt)
{
is_adj_draw = true;
break;
}
}
if (is_adj_draw)
{
return 0;
}
}
}
// Draw by insufficient mating material
if (params.detect_draw_by_insufficient_mating_material)
{
if (pos.count<ALL_PIECES>() <= 4)
{
int num_pieces = pos.count<ALL_PIECES>();
// (1) KvK
if (num_pieces == 2)
{
return 0;
}
// (2) KvK + 1 minor piece
if (num_pieces == 3)
{
int minor_pc = pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE) +
pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK);
if (minor_pc == 1)
{
return 0;
}
}
// (3) KBvKB, bishops of the same color
else if (num_pieces == 4)
{
if (pos.count<BISHOP>(WHITE) == 1 && pos.count<BISHOP>(BLACK) == 1)
{
// Color of bishops is black.
if ((pos.pieces(WHITE, BISHOP) & DarkSquares)
&& (pos.pieces(BLACK, BISHOP) & DarkSquares))
{
return 0;
}
// Color of bishops is white.
if ((pos.pieces(WHITE, BISHOP) & ~DarkSquares)
&& (pos.pieces(BLACK, BISHOP) & ~DarkSquares))
{
return 0;
}
}
}
}
}
return nullopt;
}
vector<uint8_t> TrainingDataGenerator::generate_random_move_flags(PRNG& prng)
{
vector<uint8_t> random_move_flag;
// Depending on random move selection parameters setup
// the array of flags that indicates whether a random move
// be taken at a given ply.
// Make an array like a[0] = 0 ,a[1] = 1, ...
// Fisher-Yates shuffle and take out the first N items.
// Actually, I only want N pieces, so I only need
// to shuffle the first N pieces with Fisher-Yates.
vector<int> a;
a.reserve((size_t)params.random_move_maxply);
// random_move_minply ,random_move_maxply is specified by 1 origin,
// Note that we are handling 0 origin here.
for (int i = std::max(params.random_move_minply - 1, 0); i < params.random_move_maxply; ++i)
{
a.push_back(i);
}
// In case of Apery random move, insert() may be called random_move_count times.
// Reserve only the size considering it.
random_move_flag.resize((size_t)params.random_move_maxply + params.random_move_count);
// A random move that exceeds the size() of a[] cannot be applied, so limit it.
for (int i = 0; i < std::min(params.random_move_count, (int)a.size()); ++i)
{
swap(a[i], a[prng.rand((uint64_t)a.size() - i) + i]);
random_move_flag[a[i]] = true;
}
return random_move_flag;
}
optional<Move> TrainingDataGenerator::choose_random_move(
PRNG& prng,
Position& pos,
std::vector<uint8_t>& random_move_flag,
int ply,
int& random_move_c)
{
optional<Move> random_move;
// Randomly choose one from legal move
if (
// 1. Random move of random_move_count times from random_move_minply to random_move_maxply
(params.random_move_minply != -1 && ply < (int)random_move_flag.size() && random_move_flag[ply]) ||
// 2. A mode to perform random move of random_move_count times after leaving the startpos
(params.random_move_minply == -1 && random_move_c < params.random_move_count))
{
++random_move_c;
// It's not a mate, so there should be one legal move...
if (params.random_multi_pv == 0)
{
// Normal random move
MoveList<LEGAL> list(pos);
// I don't really know the goodness and badness of making this the Apery method.
if (params.random_move_like_apery == 0
|| prng.rand(params.random_move_like_apery) != 0)
{
// Normally one move from legal move
random_move = list.at((size_t)prng.rand((uint64_t)list.size()));
}
else
{
// if you can move the king, move the king
Move moves[8]; // Near 8
Move* p = &moves[0];
for (auto& m : list)
{
if (type_of(pos.moved_piece(m)) == KING)
{
*(p++) = m;
}
}
size_t n = p - &moves[0];
if (n != 0)
{
// move to move the king
random_move = moves[prng.rand(n)];
// In Apery method, at this time there is a 1/2 chance
// that the opponent will also move randomly
if (prng.rand(2) == 0)
{
// Is it a simple hack to add a "1" next to random_move_flag[ply]?
random_move_flag.insert(random_move_flag.begin() + ply + 1, 1, true);
}
}
else
{
// Normally one move from legal move
random_move = list.at((size_t)prng.rand((uint64_t)list.size()));
}
}
}
else
{
Search::search(pos, params.random_multi_pv_depth, params.random_multi_pv, params.random_multi_pv_nodes);
// Select one from the top N hands of root Moves
auto& rm = pos.this_thread()->rootMoves;
uint64_t s = min((uint64_t)rm.size(), (uint64_t)params.random_multi_pv);
for (uint64_t i = 1; i < s; ++i)
{
// The difference from the evaluation value of rm[0] must
// be within the range of random_multi_pv_diff.
// It can be assumed that rm[x].score is arranged in descending order.
if (rm[0].score > rm[i].score + params.random_multi_pv_diff)
{
s = i;
break;
}
}
random_move = rm[prng.rand(s)].pv[0];
}
}
return random_move;
}
// Write out the phases loaded in sfens to a file.
// result: win/loss in the next phase after the final phase in sfens
// 1 when winning. -1 when losing. Pass 0 for a draw.
// Return value: true if the specified number of
// sfens has already been reached and the process ends.
bool TrainingDataGenerator::commit_psv(
Thread& th,
PSVector& sfens,
int8_t result,
std::atomic<uint64_t>& counter,
uint64_t limit,
Color result_color)
{
if (!params.write_out_draw_game_in_training_data_generation && result == 0)
{
// We didn't write anything so why quit.
return false;
}
auto side_to_move_from_sfen = [](auto& sfen){
return (Color)(sfen.sfen.data[0] & 1);
};
// From the final stage (one step before) to the first stage, give information on the outcome of the game for each stage.
// The phases stored in sfens are assumed to be continuous (in order).
for (auto it = sfens.rbegin(); it != sfens.rend(); ++it)
{
// The side to move is packed as the lowest bit of the first byte
const Color side_to_move = side_to_move_from_sfen(*it);
it->game_result = side_to_move == result_color ? result : -result;
}
const bool frc = th.rootPos.is_chess960();
// Write sfens in move order to make potential compression easier
for (auto& sfen : sfens)
{
// Skip positions with castling bestmove in FRC so that we don't
// need to support it in the trainer.
if (frc && type_of((Move)sfen.move) == CASTLING)
{
continue;
}
// Return true if there is already enough data generated.
const auto iter = counter.fetch_add(1);
if (iter >= limit)
return true;
// because `iter` was done, now we do one more
maybe_report(iter + 1);
// Write out one sfen.
sfen_writer.write(th.id(), sfen);
}
return false;
}
void TrainingDataGenerator::initial_report()
{
out = sync_region_cout.new_region();
const auto now_time = now();
out
<< '\n'
<< 0 << " sfens, "
<< "at " << now_string() << endl;
last_stats_report_time = now_time;
out = sync_region_cout.new_region();
}
void TrainingDataGenerator::report(uint64_t done, uint64_t new_done)
{
const auto now_time = now();
const TimePoint elapsed = now_time - last_stats_report_time + 1;
out
<< '\n'
<< done << " sfens, "
<< new_done * 1000 / elapsed << " sfens/second, "
<< "at " << now_string() << endl;
last_stats_report_time = now_time;
out = sync_region_cout.new_region();
}
void TrainingDataGenerator::maybe_report(uint64_t done)
{
if (done % REPORT_DOT_EVERY == 0)
{
std::lock_guard lock(stats_mutex);
if (last_stats_report_time == 0)
{
last_stats_report_time = now();
out = sync_region_cout.new_region();
}
if (done != 0)
{
out << '.';
if (done % REPORT_STATS_EVERY == 0)
{
report(done, REPORT_STATS_EVERY);
}
}
}
}
// Command to generate a game record
void generate_training_data(istringstream& is)
{
// Number of generated game records default = 8 billion phases (Ponanza specification)
uint64_t loop_max = 8000000000UL;
uint64_t time_max = 8000000000UL;
TrainingDataGenerator::Params params;
// Add a random number to the end of the file name.
bool random_file_name = false;
std::string sfen_format = "binpack";
string token;
while (true)
{
token = "";
is >> token;
if (token == "")
break;
if (token == "depth")
{
is >> params.search_depth_min;
params.search_depth_max = params.search_depth_min;
}
else if (token == "min_depth")
is >> params.search_depth_min;
else if (token == "max_depth")
is >> params.search_depth_max;
else if (token == "nodes")
is >> params.nodes;
else if (token == "count")
is >> loop_max;
else if (token == "max_time_seconds")
is >> time_max;
else if (token == "max_time_minutes")
{
is >> time_max;
time_max *= 60;
}
else if (token == "max_time_hours")
{
is >> time_max;
time_max *= 3600;
}
else if (token == "output_file_name")
is >> params.output_file_name;
else if (token == "eval_limit")
is >> params.eval_limit;
else if (token == "random_move_min_ply")
is >> params.random_move_minply;
else if (token == "random_move_max_ply")
is >> params.random_move_maxply;
else if (token == "random_move_count")
is >> params.random_move_count;
else if (token == "random_move_like_apery")
is >> params.random_move_like_apery;
else if (token == "random_multi_pv")
is >> params.random_multi_pv;
else if (token == "random_multi_pv_diff")
is >> params.random_multi_pv_diff;
else if (token == "random_multi_pv_depth")
is >> params.random_multi_pv_depth;
else if (token == "random_multi_pv_nodes")
is >> params.random_multi_pv_nodes;
else if (token == "write_min_ply")
is >> params.write_minply;
else if (token == "write_max_ply")
is >> params.write_maxply;
else if (token == "save_every")
is >> params.save_every;
else if (token == "book")
is >> params.book;
else if (token == "random_file_name")
is >> random_file_name;
else if (token == "keep_draws")
is >> params.write_out_draw_game_in_training_data_generation;
else if (token == "adjudicate_draws_by_score")
is >> params.detect_draw_by_consecutive_low_score;
else if (token == "adjudicate_draws_by_insufficient_material")
is >> params.detect_draw_by_insufficient_mating_material;
else if (token == "data_format")
is >> sfen_format;
else if (token == "seed")
is >> params.seed;
else if (token == "set_recommended_uci_options")
{
UCI::setoption("Skill Level", "20");
UCI::setoption("UCI_LimitStrength", "false");
UCI::setoption("PruneAtShallowDepth", "false");
UCI::setoption("EnableTranspositionTable", "true");
}
else
{
cout << "ERROR: Unknown option " << token << ". Exiting...\n";
return;
}
}
if (!sfen_format.empty())
{
if (sfen_format == "bin")
params.sfen_format = SfenOutputType::Bin;
else if (sfen_format == "binpack")
params.sfen_format = SfenOutputType::Binpack;
else
cout << "WARNING: Unknown sfen format `" << sfen_format << "`. Using bin\n";
}
if (random_file_name)
{
// Give a random number to output_file_name at this point.
// Do not use std::random_device(). Because it always the same integers on MinGW.
PRNG r(params.seed);
// Just in case, reassign the random numbers.
for (int i = 0; i < 10; ++i)
r.rand(1);
auto to_hex = [](uint64_t u) {
std::stringstream ss;
ss << std::hex << u;
return ss.str();
};
// I don't want to wear 64bit numbers by accident, so I'next_move going to make a 64bit number 2 just in case.
params.output_file_name += "_" + to_hex(r.rand<uint64_t>()) + to_hex(r.rand<uint64_t>());
}
params.enforce_constraints();
std::cout << "INFO: Executing generate_training_data command\n";
std::cout << "INFO: Parameters:\n";
std::cout
<< " - search_depth_min = " << params.search_depth_min << endl
<< " - search_depth_max = " << params.search_depth_max << endl
<< " - nodes = " << params.nodes << endl
<< " - count = " << loop_max << endl
<< " - max_time_seconds = " << time_max << endl
<< " - eval_limit = " << params.eval_limit << endl
<< " - num threads (UCI) = " << params.num_threads << endl
<< " - random_move_min_ply = " << params.random_move_minply << endl
<< " - random_move_max_ply = " << params.random_move_maxply << endl
<< " - random_move_count = " << params.random_move_count << endl
<< " - random_move_like_apery = " << params.random_move_like_apery << endl
<< " - random_multi_pv = " << params.random_multi_pv << endl
<< " - random_multi_pv_diff = " << params.random_multi_pv_diff << endl
<< " - random_multi_pv_depth = " << params.random_multi_pv_depth << endl
<< " - random_multi_pv_nodes = " << params.random_multi_pv_nodes << endl
<< " - write_min_ply = " << params.write_minply << endl
<< " - write_max_ply = " << params.write_maxply << endl
<< " - book = " << params.book << endl
<< " - output_file_name = " << params.output_file_name << endl
<< " - save_every = " << params.save_every << endl
<< " - random_file_name = " << random_file_name << endl
<< " - write_drawn_games = " << params.write_out_draw_game_in_training_data_generation << endl
<< " - draw by low score = " << params.detect_draw_by_consecutive_low_score << endl
<< " - draw by insuff. mat. = " << params.detect_draw_by_insufficient_mating_material << endl;
// Show if the training data generator uses NNUE.
Eval::NNUE::verify();
Threads.main()->ponder = false;
TrainingDataGenerator gensfen(params);
gensfen.generate(loop_max, time_max);
std::cout << "INFO: generate_training_data finished." << endl;
}
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef _GENSFEN_H_
#define _GENSFEN_H_
#include "position.h"
#include <sstream>
namespace Stockfish::Tools {
// Automatic generation of teacher position
void generate_training_data(std::istringstream& is);
}
#endif
+498
View File
@@ -0,0 +1,498 @@
#include "training_data_generator_nonpv.h"
#include "sfen_writer.h"
#include "packed_sfen.h"
#include "opening_book.h"
#include "misc.h"
#include "position.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue.h"
#include "syzygy/tbprobe.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <limits>
#include <list>
#include <memory>
#include <optional>
#include <random>
#include <shared_mutex>
#include <sstream>
#include <unordered_set>
using namespace std;
namespace Stockfish::Tools
{
// Class to generate sfen with multiple threads
struct TrainingDataGeneratorNonPv
{
struct Params
{
// The depth for search on the fens gathered during exploration
int search_depth = 3;
// the min/max number of nodes to use for exploration per ply
int exploration_min_nodes = 5000;
int exploration_max_nodes = 15000;
// The pct of positions explored that are saved for rescoring
float exploration_save_rate = 0.01;
// Upper limit of evaluation value of generated situation
int eval_limit = 4000;
// the upper limit on evaluation during exploration selfplay
int exploration_eval_limit = 4000;
int exploration_max_ply = 200;
int exploration_min_pieces = 8;
std::string output_file_name = "training_data_nonpv";
SfenOutputType sfen_format = SfenOutputType::Binpack;
std::string seed;
int num_threads;
std::string book;
bool smart_fen_skipping = false;
void enforce_constraints()
{
// Limit the maximum to a one-stop score. (Otherwise you might not end the loop)
eval_limit = std::min(eval_limit, (int)mate_in(2));
exploration_eval_limit = std::min(eval_limit, (int)mate_in(2));
exploration_min_nodes = std::max(100, exploration_min_nodes);
exploration_max_nodes = std::max(exploration_min_nodes, exploration_max_nodes);
num_threads = Options["Threads"];
}
};
static constexpr uint64_t REPORT_DOT_EVERY = 5000;
static constexpr uint64_t REPORT_STATS_EVERY = 200000;
static_assert(REPORT_STATS_EVERY % REPORT_DOT_EVERY == 0);
TrainingDataGeneratorNonPv(
const Params& prm
) :
params(prm),
prng(prm.seed),
sfen_writer(prm.output_file_name, prm.num_threads, std::numeric_limits<uint64_t>::max(), prm.sfen_format)
{
if (!prm.book.empty())
{
opening_book = open_opening_book(prm.book, prng);
if (opening_book == nullptr)
{
std::cout << "WARNING: Failed to open opening book " << prm.book << ". Falling back to startpos.\n";
}
}
// Output seed to veryfy by the user if it's not identical by chance.
std::cout << prng << std::endl;
}
void generate(uint64_t limit);
private:
Params params;
PRNG prng;
std::mutex stats_mutex;
TimePoint last_stats_report_time;
// sfen exporter
SfenWriter sfen_writer;
SynchronizedRegionLogger::Region out;
std::unique_ptr<OpeningBook> opening_book;
static void set_gensfen_search_limits();
void generate_worker(
Thread& th,
std::atomic<uint64_t>& counter,
uint64_t limit);
bool commit_psv(
Thread& th,
PSVector& sfens,
std::atomic<uint64_t>& counter,
uint64_t limit);
PSVector do_exploration(
Thread& th,
int count);
void report(uint64_t done, uint64_t new_done);
void maybe_report(uint64_t done);
};
void TrainingDataGeneratorNonPv::set_gensfen_search_limits()
{
// About Search::Limits
// Be careful because this member variable is global and affects other threads.
auto& limits = Search::Limits;
// Make the search equivalent to the "go infinite" command. (Because it is troublesome if time management is done)
limits.infinite = true;
// Since PV is an obstacle when displayed, erase it.
limits.silent = true;
// If you use this, it will be compared with the accumulated nodes of each thread. Therefore, do not use it.
limits.nodes = 0;
// depth is also processed by the one passed as an argument of Tools::search().
limits.depth = 0;
}
void TrainingDataGeneratorNonPv::generate(uint64_t limit)
{
last_stats_report_time = 0;
set_gensfen_search_limits();
std::atomic<uint64_t> counter{0};
Threads.execute_with_workers([&counter, limit, this](Thread& th) {
generate_worker(th, counter, limit);
});
Threads.wait_for_workers_finished();
sfen_writer.flush();
if (limit % REPORT_STATS_EVERY != 0)
{
report(limit, limit % REPORT_STATS_EVERY);
}
std::cout << std::endl;
}
PSVector TrainingDataGeneratorNonPv::do_exploration(
Thread& th,
int count)
{
constexpr int max_depth = 30;
PSVector psv;
std::vector<StateInfo, AlignedAllocator<StateInfo>> states(
max_depth + MAX_PLY /* == search_depth_min + α */);
th.set_eval_callback([this, &psv](Position& pos) {
if ((double)prng.rand<uint64_t>() / std::numeric_limits<uint64_t>::max() < params.exploration_save_rate)
{
psv.emplace_back();
pos.sfen_pack(psv.back().sfen, pos.is_chess960());
}
});
auto& pos = th.rootPos;
StateInfo si;
const bool frc = Options["UCI_Chess960"];
for (int i = 0; i < count; ++i)
{
if (opening_book != nullptr)
{
auto& fen = opening_book->next_fen();
pos.set(fen, frc, &si, &th);
}
else
{
pos.set(StartFEN, frc, &si, &th);
}
for(int ply = 0; ply < params.exploration_max_ply; ++ply)
{
auto nodes = prng.rand(params.exploration_max_nodes - params.exploration_min_nodes + 1) + params.exploration_min_nodes;
auto [search_value, search_pv] = Search::search(pos, max_depth, 1, nodes);
if (search_pv.empty())
{
break;
}
if (std::abs(search_value) > params.exploration_eval_limit)
{
break;
}
pos.do_move(search_pv[0], states[ply]);
if (popcount(pos.pieces()) < params.exploration_min_pieces)
{
break;
}
}
}
th.clear_eval_callback();
return psv;
}
void TrainingDataGeneratorNonPv::generate_worker(
Thread& th,
std::atomic<uint64_t>& counter,
uint64_t limit)
{
constexpr int exploration_batch_size = 1;
StateInfo si;
PSVector psv;
// end flag
bool quit = false;
const bool frc = Options["UCI_Chess960"];
// repeat until the specified number of times
while (!quit)
{
// It is necessary to set a dependent thread for Position.
// When parallelizing, Threads (since this is a vector<Thread*>,
// Do the same for up to Threads[0]...Threads[thread_num-1].
auto& pos = th.rootPos;
auto packed_sfens = do_exploration(th, exploration_batch_size);
psv.clear();
for (auto& ps : packed_sfens)
{
pos.set_from_packed_sfen(ps.sfen, &si, &th, frc);
pos.state()->rule50 = 0;
if (params.smart_fen_skipping && pos.checkers())
{
continue;
}
auto [search_value, search_pv] = Search::search(pos, params.search_depth, 1);
if (search_pv.empty())
{
continue;
}
if (std::abs(search_value) > params.eval_limit)
{
continue;
}
if (params.smart_fen_skipping && pos.capture_or_promotion(search_pv[0]))
{
continue;
}
auto& new_ps = psv.emplace_back();
pos.sfen_pack(new_ps.sfen, pos.is_chess960());
new_ps.score = search_value;
new_ps.move = search_pv[0];
new_ps.gamePly = 1;
new_ps.game_result = 0;
new_ps.padding = 0;
}
quit = commit_psv(th, psv, counter, limit);
}
}
// Write out the phases loaded in sfens to a file.
// result: win/loss in the next phase after the final phase in sfens
// 1 when winning. -1 when losing. Pass 0 for a draw.
// Return value: true if the specified number of
// sfens has already been reached and the process ends.
bool TrainingDataGeneratorNonPv::commit_psv(
Thread& th,
PSVector& sfens,
std::atomic<uint64_t>& counter,
uint64_t limit)
{
const bool frc = th.rootPos.is_chess960();
// Write sfens in move order to make potential compression easier
for (auto& sfen : sfens)
{
// Skip positions with castling bestmove in FRC so that we don't
// need to support it in the trainer.
if (frc && type_of((Move)sfen.move) == CASTLING)
{
continue;
}
// Return true if there is already enough data generated.
const auto iter = counter.fetch_add(1);
if (iter >= limit)
return true;
// because `iter` was done, now we do one more
maybe_report(iter + 1);
// Write out one sfen.
sfen_writer.write(th.id(), sfen);
}
return false;
}
void TrainingDataGeneratorNonPv::report(uint64_t done, uint64_t new_done)
{
const auto now_time = now();
const TimePoint elapsed = now_time - last_stats_report_time + 1;
out
<< endl
<< done << " sfens, "
<< new_done * 1000 / elapsed << " sfens/second, "
<< "at " << now_string() << sync_endl;
last_stats_report_time = now_time;
out = sync_region_cout.new_region();
}
void TrainingDataGeneratorNonPv::maybe_report(uint64_t done)
{
if (done % REPORT_DOT_EVERY == 0)
{
std::lock_guard lock(stats_mutex);
if (last_stats_report_time == 0)
{
last_stats_report_time = now();
out = sync_region_cout.new_region();
}
if (done != 0)
{
out << '.';
if (done % REPORT_STATS_EVERY == 0)
{
report(done, REPORT_STATS_EVERY);
}
}
}
}
// Command to generate a game record
void generate_training_data_nonpv(istringstream& is)
{
// Number of generated game records default = 8 billion phases (Ponanza specification)
TrainingDataGeneratorNonPv::Params params;
uint64_t count = 1'000'000;
// Add a random number to the end of the file name.
std::string sfen_format = "binpack";
string token;
while (true)
{
token = "";
is >> token;
if (token == "")
break;
if (token == "depth")
is >> params.search_depth;
else if (token == "count")
is >> count;
else if (token == "output_file")
is >> params.output_file_name;
else if (token == "exploration_eval_limit")
is >> params.exploration_eval_limit;
else if (token == "eval_limit")
is >> params.eval_limit;
else if (token == "exploration_min_nodes")
is >> params.exploration_min_nodes;
else if (token == "exploration_max_nodes")
is >> params.exploration_max_nodes;
else if (token == "exploration_min_pieces")
is >> params.exploration_min_pieces;
else if (token == "exploration_save_rate")
is >> params.exploration_save_rate;
else if (token == "book")
is >> params.book;
else if (token == "data_format")
is >> sfen_format;
else if (token == "seed")
is >> params.seed;
else if (token == "smart_fen_skipping")
params.smart_fen_skipping = true;
else if (token == "set_recommended_uci_options")
{
UCI::setoption("Skill Level", "20");
UCI::setoption("UCI_LimitStrength", "false");
UCI::setoption("PruneAtShallowDepth", "false");
UCI::setoption("EnableTranspositionTable", "true");
}
else
{
cout << "ERROR: Unknown option " << token << ". Exiting...\n";
return;
}
}
if (!sfen_format.empty())
{
if (sfen_format == "bin")
params.sfen_format = SfenOutputType::Bin;
else if (sfen_format == "binpack")
params.sfen_format = SfenOutputType::Binpack;
else
cout << "WARNING: Unknown sfen format `" << sfen_format << "`. Using bin\n";
}
params.enforce_constraints();
std::cout << "INFO: Executing generate_training_data_nonpv command\n";
std::cout << "INFO: Parameters:\n";
std::cout
<< " - search_depth = " << params.search_depth << endl
<< " - output_file = " << params.output_file_name << endl
<< " - exploration_eval_limit = " << params.exploration_eval_limit << endl
<< " - eval_limit = " << params.eval_limit << endl
<< " - exploration_min_nodes = " << params.exploration_min_nodes << endl
<< " - exploration_max_nodes = " << params.exploration_max_nodes << endl
<< " - exploration_min_pieces = " << params.exploration_min_pieces << endl
<< " - exploration_save_rate = " << params.exploration_save_rate << endl
<< " - book = " << params.book << endl
<< " - data_format = " << sfen_format << endl
<< " - seed = " << params.seed << endl
<< " - count = " << count << endl;
// Show if the training data generator uses NNUE.
Eval::NNUE::verify();
Threads.main()->ponder = false;
TrainingDataGeneratorNonPv gensfen(params);
gensfen.generate(count);
std::cout << "INFO: generate_training_data_nonpv finished." << endl;
}
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef _GENSFEN_NONPV_H_
#define _GENSFEN_NONPV_H_
#include <sstream>
namespace Stockfish::Tools {
// Automatic generation of teacher position
void generate_training_data_nonpv(std::istringstream& is);
}
#endif
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
#ifndef _TRANSFORM_H_
#define _TRANSFORM_H_
#include <sstream>
namespace Stockfish::Tools {
void transform(std::istringstream& is);
}
#endif
+122
View File
@@ -0,0 +1,122 @@
#include "validate_training_data.h"
#include "uci.h"
#include "misc.h"
#include "thread.h"
#include "position.h"
#include "tt.h"
#include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue.h"
#include "syzygy/tbprobe.h"
#include <sstream>
#include <fstream>
#include <unordered_set>
#include <iomanip>
#include <list>
#include <cmath> // std::exp(),std::pow(),std::log()
#include <cstring> // memcpy()
#include <memory>
#include <limits>
#include <optional>
#include <chrono>
#include <random>
#include <regex>
#include <filesystem>
using namespace std;
namespace sys = std::filesystem;
namespace Stockfish::Tools
{
static inline const std::string plain_extension = ".plain";
static inline const std::string bin_extension = ".bin";
static inline const std::string binpack_extension = ".binpack";
static bool file_exists(const std::string& name)
{
std::ifstream f(name);
return f.good();
}
static bool ends_with(const std::string& lhs, const std::string& end)
{
if (end.size() > lhs.size()) return false;
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
}
static bool is_validation_of_type(
const std::string& input_path,
const std::string& expected_input_extension)
{
return ends_with(input_path, expected_input_extension);
}
using ValidateFunctionType = void(std::string inputPath);
static ValidateFunctionType* get_validate_function(const std::string& input_path)
{
if (is_validation_of_type(input_path, plain_extension))
return binpack::validatePlain;
if (is_validation_of_type(input_path, bin_extension))
return binpack::validateBin;
if (is_validation_of_type(input_path, binpack_extension))
return binpack::validateBinpack;
return nullptr;
}
static void validate_training_data(const std::string& input_path)
{
if(!file_exists(input_path))
{
std::cerr << "Input file does not exist.\n";
return;
}
auto func = get_validate_function(input_path);
if (func != nullptr)
{
func(input_path);
}
else
{
std::cerr << "Validation of files of this type is not supported.\n";
}
}
static void validate_training_data(const std::vector<std::string>& args)
{
if (args.size() != 1)
{
std::cerr << "Invalid arguments.\n";
std::cerr << "Usage: validate in_path\n";
return;
}
validate_training_data(args[0]);
}
void validate_training_data(istringstream& is)
{
std::vector<std::string> args;
while (true)
{
std::string token = "";
is >> token;
if (token == "")
break;
args.push_back(token);
}
validate_training_data(args);
}
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef _VALIDATE_TRAINING_DATA_H_
#define _VALIDATE_TRAINING_DATA_H_
#include <vector>
#include <string>
#include <sstream>
namespace Stockfish::Tools {
void validate_training_data(std::istringstream& is);
}
#endif
+10
View File
@@ -30,11 +30,16 @@ namespace Stockfish {
TranspositionTable TT; // Our global transposition table
bool TranspositionTable::enable_transposition_table = true;
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
/// overwriting an old position. Update is not atomic and can be racy.
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
if (!TranspositionTable::enable_transposition_table) {
return;
}
// Preserve any existing move for the same position
if (m || (uint16_t)k != key16)
move16 = (uint16_t)m;
@@ -119,6 +124,11 @@ void TranspositionTable::clear() {
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
if (!enable_transposition_table) {
found = false;
return first_entry(0);
}
TTEntry* const tte = first_entry(key);
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
+2
View File
@@ -92,6 +92,8 @@ public:
return &table[mul_hi64(key, clusterCount)].entry[0];
}
static bool enable_transposition_table;
private:
friend struct TTEntry;
+7
View File
@@ -137,6 +137,8 @@ enum Color {
WHITE, BLACK, COLOR_NB = 2
};
constexpr Color Colors[2] = { WHITE, BLACK };
enum CastlingRights {
NO_CASTLING,
WHITE_OO,
@@ -449,6 +451,11 @@ constexpr Square to_sq(Move m) {
return Square(m & 0x3F);
}
// Return relative square when turning the board 180 degrees
constexpr Square rotate180(Square sq) {
return (Square)(sq ^ 0x3F);
}
constexpr int from_to(Move m) {
return m & 0xFFF;
}
+108 -13
View File
@@ -22,15 +22,23 @@
#include <sstream>
#include <string>
#include "nnue/evaluate_nnue.h"
#include "evaluate.h"
#include "movegen.h"
#include "position.h"
#include "search.h"
#include "syzygy/tbprobe.h"
#include "thread.h"
#include "timeman.h"
#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
#include "tools/validate_training_data.h"
#include "tools/training_data_generator.h"
#include "tools/training_data_generator_nonpv.h"
#include "tools/convert.h"
#include "tools/transform.h"
#include "tools/stats.h"
using namespace std;
@@ -40,10 +48,6 @@ extern vector<string> setup_bench(const Position&, istream&);
namespace {
// FEN string of the initial position, normal chess
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// position() is called when engine receives the "position" UCI command.
// The function sets up the position described in the given FEN string ("fen")
// or the starting position ("startpos") and then makes the moves given in the
@@ -96,7 +100,7 @@ namespace {
// setoption() is called when engine receives the "setoption" UCI command. The
// function updates the UCI option ("name") to the given value ("value").
void setoption(istringstream& is) {
void setoption_from_stream(istringstream& is) {
string token, name, value;
@@ -110,10 +114,7 @@ namespace {
while (is >> token)
value += (value.empty() ? "" : " ") + token;
if (Options.count(name))
Options[name] = value;
else
sync_cout << "No such option: " << name << sync_endl;
UCI::setoption(name, value);
}
@@ -182,7 +183,7 @@ namespace {
else
trace_eval(pos);
}
else if (token == "setoption") setoption(is);
else if (token == "setoption") setoption_from_stream(is);
else if (token == "position") position(pos, is, states);
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while
}
@@ -197,6 +198,18 @@ namespace {
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
}
} // namespace
namespace UCI {
void setoption(const std::string& name, const std::string& value)
{
if (Options.count(name))
Options[name] = value;
else
sync_cout << "No such option: " << name << sync_endl;
}
// The win rate model returns the probability (per mille) of winning given an eval
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
int win_rate_model(Value v, int ply) {
@@ -213,7 +226,7 @@ namespace {
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
// Transform eval to centipawns with limited range
double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0);
double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
// Return win rate in per mille (rounded to nearest)
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
@@ -221,6 +234,68 @@ namespace {
} // namespace
// --------------------
// Call qsearch(),search() directly for testing
// --------------------
void qsearch_cmd(Position& pos)
{
cout << "qsearch : ";
auto pv = Search::qsearch(pos);
cout << "Value = " << pv.first << " , " << UCI::value(pv.first) << " , PV = ";
for (auto m : pv.second)
cout << UCI::move(m, false) << " ";
cout << endl;
}
void search_cmd(Position& pos, istringstream& is)
{
string token;
int depth = 1;
int multi_pv = (int)Options["MultiPV"];
while (is >> token)
{
if (token == "depth")
is >> depth;
if (token == "multipv")
is >> multi_pv;
}
cout << "search depth = " << depth << " , multi_pv = " << multi_pv << " : ";
auto pv = Search::search(pos, depth, multi_pv);
cout << "Value = " << pv.first << " , " << UCI::value(pv.first) << " , PV = ";
for (auto m : pv.second)
cout << UCI::move(m, false) << " ";
cout << endl;
}
void search_mcts_cmd(Position& pos, istringstream& is)
{
string token;
int nodes = 1000;
int leafDepth = 3;
float explorationFactor = 0.25f;
while (is >> token)
{
if (token == "nodes")
is >> nodes;
if (token == "leaf_depth")
is >> leafDepth;
if (token == "exploration_factor")
is >> explorationFactor;
}
cout << "search nodes = " << nodes << " , leaf_depth = " << leafDepth << " :\n";
auto continuations = Search::MCTS::search_mcts_multipv(pos, nodes, leafDepth, explorationFactor);
for (auto&& [numVisits, value, actionValue, pv] : continuations)
{
cout << "NumVisits = " << numVisits << " , Value = " << UCI::value(value) << " , ActionValue = " << actionValue << " , PV = ";
for (auto m : pv)
cout << UCI::move(m, false) << " ";
cout << endl;
}
cout << endl;
}
/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
@@ -264,7 +339,7 @@ void UCI::loop(int argc, char* argv[]) {
<< "\n" << Options
<< "\nuciok" << sync_endl;
else if (token == "setoption") setoption(is);
else if (token == "setoption") setoption_from_stream(is);
else if (token == "go") go(pos, is, states);
else if (token == "position") position(pos, is, states);
else if (token == "ucinewgame") Search::clear();
@@ -285,6 +360,26 @@ void UCI::loop(int argc, char* argv[]) {
filename = f;
Eval::NNUE::save_eval(filename);
}
else if (token == "generate_training_data") Tools::generate_training_data(is);
else if (token == "generate_training_data_nonpv") Tools::generate_training_data_nonpv(is);
else if (token == "convert") Tools::convert(is);
else if (token == "validate_training_data") Tools::validate_training_data(is);
else if (token == "convert_bin") Tools::convert_bin(is);
else if (token == "convert_plain") Tools::convert_plain(is);
else if (token == "convert_bin_from_pgn_extract") Tools::convert_bin_from_pgn_extract(is);
else if (token == "transform") Tools::transform(is);
else if (token == "gather_statistics") Tools::Stats::gather_statistics(is);
// Command to call qsearch(),search() directly for testing
else if (token == "qsearch") qsearch_cmd(pos);
else if (token == "search_mcts") search_mcts_cmd(pos, is);
else if (token == "search") search_cmd(pos, is);
else if (token == "tasktest")
{
Threads.execute_with_workers([](auto& th) {
std::cout << th.id() << '\n';
});
}
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
sync_cout << "\nStockfish is a powerful chess engine and free software licensed under the GNU GPLv3."
"\nStockfish is normally used with a separate graphical user interface (GUI)."
+1
View File
@@ -75,6 +75,7 @@ std::string move(Move m, bool chess960);
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
std::string wdl(Value v, int ply);
Move to_move(const Position& pos, std::string& str);
void setoption(const std::string& name, const std::string& value);
} // namespace UCI
+23 -2
View File
@@ -21,6 +21,7 @@
#include <ostream>
#include <sstream>
#include "nnue/evaluate_nnue.h"
#include "evaluate.h"
#include "misc.h"
#include "search.h"
@@ -45,6 +46,12 @@ void on_threads(const Option& o) { Threads.set(size_t(o)); }
void on_tb_path(const Option& o) { Tablebases::init(o); }
void on_use_NNUE(const Option& ) { Eval::NNUE::init(); }
void on_eval_file(const Option& ) { Eval::NNUE::init(); }
void on_prune_at_shallow_depth(const Option& o) {
Search::prune_at_shallow_depth = o;
}
void on_enable_transposition_table(const Option& o) {
TranspositionTable::enable_transposition_table = o;
}
/// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
@@ -79,8 +86,22 @@ void init(OptionsMap& o) {
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
o["Use NNUE"] << Option(true, on_use_NNUE);
o["Use NNUE"] << Option("true var true var false var pure", "true", on_use_NNUE);
o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
// When the evaluation function is loaded at the ucinewgame timing, it is necessary to convert the new evaluation function.
// I want to hit the test eval convert command, but there is no new evaluation function
// It ends abnormally before executing this command.
// Therefore, with this hidden option, you can suppress the loading of the evaluation function when ucinewgame,
// Hit the test eval convert command.
o["SkipLoadingEval"] << Option(false);
// When learning the evaluation function, you can change the folder to save the evaluation function.
// Evalsave by default. This folder shall be prepared in advance.
// Automatically create a folder under this folder like "0/", "1/", ... and save the evaluation function file there.
o["EvalSaveDir"] << Option("evalsave");
// Prune at shallow depth on PV nodes. False is recommended when using fixed depth search.
o["PruneAtShallowDepth"] << Option(true, on_prune_at_shallow_depth);
// Enable transposition table.
o["EnableTranspositionTable"] << Option(true, on_enable_transposition_table);
}
@@ -134,7 +155,7 @@ Option::operator double() const {
}
Option::operator std::string() const {
assert(type == "string");
assert(type == "check" || type == "spin" || type == "combo" || type == "button" || type == "string");
return currentValue;
}
+65 -3
View File
@@ -16,6 +16,9 @@ case $1 in
exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full'
postfix='1>/dev/null'
threads="1"
bench_depth=5
go_depth=10
tt_size=16
;;
--valgrind-thread)
echo "valgrind-thread testing started"
@@ -23,6 +26,9 @@ case $1 in
exeprefix='valgrind --fair-sched=try --error-exitcode=42'
postfix='1>/dev/null'
threads="2"
bench_depth=5
go_depth=10
tt_size=16
;;
--sanitizer-undefined)
echo "sanitizer-undefined testing started"
@@ -30,6 +36,9 @@ case $1 in
exeprefix=''
postfix='2>&1 | grep -A50 "runtime error:"'
threads="1"
bench_depth=8
go_depth=20
tt_size=128
;;
--sanitizer-thread)
echo "sanitizer-thread testing started"
@@ -37,6 +46,9 @@ case $1 in
exeprefix=''
postfix='2>&1 | grep -A50 "WARNING: ThreadSanitizer:"'
threads="2"
bench_depth=8
go_depth=20
tt_size=128
cat << EOF > tsan.supp
race:Stockfish::TTEntry::move
@@ -70,7 +82,7 @@ for args in "eval" \
"go depth 10" \
"go movetime 1000" \
"go wtime 8000 btime 8000 winc 500 binc 500" \
"bench 128 $threads 8 default depth"
"bench $tt_size $threads $bench_depth default depth"
do
echo "$prefix $exeprefix ./stockfish $args $postfix"
@@ -121,7 +133,7 @@ cat << EOF > syzygy.exp
send "uci\n"
send "setoption name SyzygyPath value ../tests/syzygy/\n"
expect "info string Found 35 tablebases" {} timeout {exit 1}
send "bench 128 1 8 default depth\n"
send "bench $tt_size 1 $bench_depth default depth\n"
send "quit\n"
expect eof
@@ -130,7 +142,57 @@ cat << EOF > syzygy.exp
exit \$value
EOF
for exp in game.exp syzygy.exp
# generate_training_data testing 01
cat << EOF > data_generation01.exp
set timeout 240
spawn $exeprefix ./stockfish
send "uci\n"
expect "uciok"
send "setoption name Threads value $threads\n"
send "setoption name Use NNUE value false\n"
send "isready\n"
send "generate_training_data depth 3 count 100 keep_draws 1 eval_limit 32000 output_file_name training_data/training_data.bin output_format bin\n"
expect "INFO: Gensfen finished."
send "convert_plain targetfile training_data/training_data.bin output_file_name training_data.txt\n"
expect "all done"
send "generate_training_data depth 3 count 100 keep_draws 1 eval_limit 32000 output_file_name training_data/training_data.binpack output_format binpack\n"
expect "INFO: Gensfen finished."
send "quit\n"
expect eof
# return error code of the spawned program, useful for valgrind
lassign [wait] pid spawnid os_error_flag value
exit \$value
EOF
# generate_training_data testing 02
cat << EOF > data_generation02.exp
set timeout 240
spawn $exeprefix ./stockfish
send "uci\n"
expect "uciok"
send "setoption name Threads value $threads\n"
send "setoption name Use NNUE value true\n"
send "isready\n"
send "generate_training_data depth 4 count 50 keep_draws 1 eval_limit 32000 output_file_name validation_data/validation_data.bin output_format bin\n"
expect "INFO: Gensfen finished."
send "generate_training_data depth 4 count 50 keep_draws 1 eval_limit 32000 output_file_name validation_data/validation_data.binpack output_format binpack\n"
expect "INFO: Gensfen finished."
send "quit\n"
expect eof
# return error code of the spawned program, useful for valgrind
lassign [wait] pid spawnid os_error_flag value
exit \$value
EOF
for exp in game.exp data_generation01.exp data_generation02.exp
do
echo "$prefix expect $exp $postfix"