Compare commits

...
Author SHA1 Message Date
Joost VandeVondeleandGitHub e904a2a188 Merge pull request #6497 from sesse/cluster-master-fmt
Merge cluster up to latest master
2026-01-02 09:41:20 +01:00
Steinar H. Gunderson 7ed6af120a Fix -Wunused-parameter warning.
When compiling the cluster branch but in non-MPI mode, we'd get some
warnings in cluster.h about unused ThreadPool parameters.

No functional change.
2026-01-02 09:29:38 +01:00
Steinar H. Gunderson d3a46e40b2 Update cluster copyright year.
No functional change.
2026-01-02 09:29:33 +01:00
Steinar H. Gunderson bd3fd8ba79 Extract bestMove and ponderMove after the PV.
Extracting ponderMove may have the unintentional effect of extending the
PV by one move (which should not be displayed), so we need to extract it
after we've extracted the PV. We still need to both before the MPI exchange,
though.

Patch by vondele, merely committed by me.
2026-01-02 09:24:56 +01:00
Steinar H. Gunderson 3d577337c2 Merge branch 'master' into cluster 2026-01-01 17:33:27 +01:00
Steinar H. Gunderson bbdab9889a Unbreak MPI compilation.
3c2c7b01bd deleted the TTCache default constructor, but we need it
when MPI is active. This wasn't discovered during the merging,
and it is hard to put it properly into the commit history due to
rebase/rerere shortcomings. (This means that some of the merge
commits will not compile with MPI on, and this patch needs to be
added on top if bisecting.)
2025-12-26 23:20:44 +01:00
Steinar H. Gunderson 1b3830fad4 Merge commit 'c467fe5ba42545827d769c360aa404767308ac18' into cluster 2025-12-25 20:22:33 +01:00
Steinar H. Gunderson bf5f8c5543 Merge commit '5297ba0a1a1aa0a15332e0d64ce6b32952342cac' into cluster 2025-12-25 20:22:26 +01:00
Steinar H. Gunderson 0bcfc6c651 Merge commit 'abd835dcbc3a28481224f6253b00b7420d062513' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:19:41 +01:00
Steinar H. Gunderson a8f8f97eb1 Merge commit '8e5392d79a36aba5b997cf6fb590937e3e624e80' into cluster 2025-12-25 20:19:19 +01:00
Steinar H. Gunderson 8cc99e1314 Merge commit '69a01b88f35db2a5003d42116f573207ca5c275b' into cluster 2025-12-25 20:16:11 +01:00
Steinar H. Gunderson bfc8c24999 Merge commit 'bc9f08731f10896a306bcc34e30e5606087af79a' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:13:41 +01:00
Steinar H. Gunderson 8b776c3f04 Merge commit '75edbee01e6f8cb53a2555499192ccaddb883577' into cluster 2025-12-25 20:13:34 +01:00
Steinar H. Gunderson 725acd7449 Merge commit '315f8ba4bf7d846b35f984d5e6040c14a512d9b9' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:12:54 +01:00
Steinar H. Gunderson f5a171d82e Merge commit 'e2aa1255707628aa1d70f13c88211724e34fa38e' into cluster 2025-12-25 20:12:35 +01:00
Steinar H. Gunderson b5c4fd2b7a Merge commit '8c2d21f91a5840a67c36267e5043070ffad06860' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:08:43 +01:00
Steinar H. Gunderson e1f2a899b7 Merge commit 'd2d046c2a497b2d70debde07ccc414ca633d550b' into cluster 2025-12-25 20:07:02 +01:00
Steinar H. Gunderson f44f4f356c Merge commit '44efbaddea909e146c6c41afaf458da8c9e4b4e4' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:03:12 +01:00
Steinar H. Gunderson 20d5986235 Merge commit 'd7c04a942950f1fe3f655bf8b608e8ef21c07628' into cluster 2025-12-25 20:03:00 +01:00
Steinar H. Gunderson ab03e55a60 Merge commit 'd942e13398aa5de55224c7d81bfad6b0f5b9e488' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:02:14 +01:00
Steinar H. Gunderson 7c50916a9d Merge commit '291a429cdd54f5298b8dc3d19cd08c3a64de4d10' into cluster 2025-12-25 20:02:02 +01:00
Steinar H. Gunderson 60f5e6c211 Merge commit '43b2d65d7275b11fd47c7225f8a0d19afbab4cd1' into cluster
This is the last commit before there are more conflicts.
2025-12-25 20:01:02 +01:00
Steinar H. Gunderson 0eb376ce8d Merge commit 'f00d91f8ac72de8d201f8b50968bb66b1235dc9a' into cluster 2025-12-25 20:00:51 +01:00
Steinar H. Gunderson 6653af1549 Merge commit '738ac2a10025ca58198e3d2d7f0bc70d83c2cb7f' into cluster
This is the last commit before there are more conflicts.
2025-12-25 19:59:45 +01:00
Steinar H. Gunderson 4662a58e1d Merge commit '28c07fb456855c4e082571ed7dd723a3e71fdcff' into cluster 2025-12-25 19:59:29 +01:00
Steinar H. Gunderson 21d7128260 Merge commit 'ea71a088435d4f1e51433c0a321f2afdff7814b1' into cluster
This is the last commit before there are more conflicts.
2025-12-25 19:58:36 +01:00
Steinar H. Gunderson 75dd7d80ba Merge commit 'f414d490bc0e4013c211a066623f313883f49106' into cluster 2025-12-25 19:53:08 +01:00
Steinar H. Gunderson 8a6a8e8a42 Merge commit 'b822fdf2f2f00758c794cb61a25a044424d2bc0a' into cluster
This is the last commit before there are more conflicts.
2025-12-25 19:50:17 +01:00
Steinar H. Gunderson 03732aa685 Merge commit '57e06be71f0177a69843750a9f456462d02f23b9' into cluster 2025-12-25 17:57:11 +01:00
Steinar H. Gunderson f2643877eb Merge commit '70bb317afe870c8bc1979ef955f120e4d81f504e' into cluster
This is the last commit before there are more conflicts.
2025-12-25 17:33:17 +01:00
Steinar H. Gunderson a5ea0b2b2b Merge commit 'c2611efe5c317969b583a5ff81352439f905e722' into cluster 2025-12-25 17:32:39 +01:00
Steinar H. Gunderson 110fdc486a Merge commit '16fee2a7da25c6d0267930eb9677862cb1f009c7' into cluster
This is the last commit before there are more conflicts.
2025-12-25 17:30:06 +01:00
Steinar H. Gunderson b3580e942d Merge commit 'c15113554f53890d7944c00a70d0f2d8a78916fb' into cluster 2025-12-25 17:26:35 +01:00
Steinar H. Gunderson 5d3f71f72d Merge commit '2ce47573b4d3664dca4cbc4354c8c600540d16ad' into cluster
This is the last commit before there are more conflicts.
2025-12-25 17:22:27 +01:00
Steinar H. Gunderson 34a07438c0 Merge commit '3ac75cd27d914da29280163c9d391bbca414d766' into cluster 2025-12-25 17:10:14 +01:00
Steinar H. Gunderson fa1ab27e15 Merge commit 'aff1f67997cd2584ea7c82d967ac7bfd4cc77861' into cluster
This is the last commit before there are more conflicts.
2025-12-25 16:59:53 +01:00
Steinar H. Gunderson e8bc8639fc Merge commit '93869d5d0aab2f7121bdf227def3a942c9fcde17' into cluster 2025-12-25 16:59:18 +01:00
Steinar H. Gunderson a5c89f4592 Merge commit '60351b9df901ff5278f208a9cf3a40059ff54832' into cluster
This is the last commit before there are more conflicts.
2025-12-25 16:56:54 +01:00
Steinar H. Gunderson a449238954 Merge commit '703f17975bd9c29172a27f795ca6b5a7d0a32b25' into cluster 2025-12-25 16:55:54 +01:00
Steinar H. Gunderson 0043886170 Merge commit 'e57fba7fc9be461cbb97c063b269a1e231cdd284' into cluster
This is the last commit before there are more conflicts.
2025-12-25 16:54:13 +01:00
Steinar H. Gunderson 0814961843 Merge commit '6135a0e2f830a587d2ac7a332bb62188fa924aad' into cluster 2025-12-25 16:52:43 +01:00
Steinar H. Gunderson 1cffdc1ce4 Merge commit '7e72b37e4ce3351399bb0ac08686bd84cdc4fba9' into cluster 2025-12-25 16:03:31 +01:00
Steinar H. Gunderson 0c451731c6 Merge commit '98a7bb4436f04505a17f37befab0207252e97897' into cluster
This is the last commit before there are more conflicts.
2025-12-25 16:00:06 +01:00
Steinar H. Gunderson a20c6d296a Merge commit '362a77a3450335e1940020c080bf3b7b361c594a' into cluster 2025-12-25 15:53:03 +01:00
Steinar H. Gunderson d002e92294 Merge commit 'b209f14b1ee0cda8cbd7fa3a8349e65701d1869f' into cluster
This is the last commit before there are more conflicts.
2025-12-25 15:48:01 +01:00
Steinar H. Gunderson d83e63c163 Merge commit '5d3517c601c64d026824251784dd44f0cbf14873' into cluster 2025-12-25 15:30:32 +01:00
Steinar H. Gunderson d7cf6e22fd Merge commit '5752529cabb3270e055147709ff0847e4d59ec22' into cluster
This is the last commit before there are more conflicts.
2025-12-25 15:27:42 +01:00
Steinar H. Gunderson d1b19aad2f Merge commit '2cbc20e846e46da8bfc8e254a7703a0bfad3b850' into cluster 2025-12-25 15:26:23 +01:00
Steinar H. Gunderson 8f9e750782 Merge commit '3c379e55d9d92a5704632c6255e72892a4db9a2f' into cluster
This is the last commit before there are more conflicts.
2025-12-25 15:22:41 +01:00
Steinar H. Gunderson cefd309d92 Merge commit '25361e514bffb81284d4311601a9f7a4a7ced79b' into cluster 2025-12-25 15:22:02 +01:00
Steinar H. Gunderson 46264c6ed7 Merge commit '74a8fc060465a822f0c047f908d5fb07ebc6ad96' into cluster
This is the last commit before there are more conflicts.
2025-12-25 15:16:24 +01:00
Steinar H. Gunderson 5c9ed18b48 Merge commit 'b01fdb596a196f966549f7132c042ab67962fbbd' into cluster 2025-12-25 15:16:01 +01:00
Steinar H. Gunderson 7df644a00a Merge commit '44cddbd962c738678f407a7414efa5b93f0710d9' into cluster
This is the last commit before there are more conflicts.
2025-12-25 15:10:02 +01:00
Steinar H. Gunderson 876495975e Merge commit 'c8213ba0d047569141ed58f5eb86579d976b5614' into cluster
As part of this work, we needed to rename the namespace Stockfish::Cluster
to Stockfish::Distributed, as Cluster is now a type name.
2025-12-25 00:17:08 +01:00
Steinar H. Gunderson f358a30ea0 Merge commit '7e890fd048e22bfd213d46ec8eb88f7931f0315d' into cluster
This is the last commit before there are more conflicts.
2025-12-24 16:29:33 +01:00
Steinar H. Gunderson 5034cf6593 Merge commit '66ed4312f22a951aaa01bbb87b2d730656b8f2c1' into cluster 2025-12-24 16:28:21 +01:00
Steinar H. Gunderson 52d8388101 Merge commit 'e6c83beed12a6d3d17c69bea4bcf1a397bc60c86' into cluster
This is the last commit before there are more conflicts.
2025-12-24 16:27:16 +01:00
Steinar H. Gunderson 9770899bf7 Merge commit '5688b188cc8560e107815c83a7084220fddebdb9' into cluster 2025-12-24 16:26:53 +01:00
Steinar H. Gunderson 639294636e Merge commit '36eb9bc783d35842571d0d4313349b964892d9ca' into cluster
This is the last commit before there are more conflicts.
2025-12-24 16:26:07 +01:00
Steinar H. Gunderson 75138525c7 Merge commit '00a28ae325688346e63a452b2050bd1491085359' into cluster 2025-12-24 16:25:32 +01:00
Steinar H. Gunderson 784db2cbed Merge commit 'a2a7edf4c8fa145667135bf1bc7f4f67016f7608' into cluster
This is the last commit before there are more conflicts.
2025-12-24 16:19:29 +01:00
Steinar H. Gunderson bc81aa0911 Merge commit 'a169c78b6d3b082068deb49a39aaa1fd75464c7f' into cluster 2025-12-24 16:18:37 +01:00
Steinar H. Gunderson e56697e3f1 Merge commit 'b0287dcb1c436887075962b596cf2068d2ca9ba8' into cluster
This is the last commit before there are more conflicts.
2025-12-24 15:23:14 +01:00
Steinar H. Gunderson 125f4f3df0 Merge commit 'ed79745bb9e7207b604c62758ea45dd5c597ed8d' into cluster 2025-12-24 15:22:12 +01:00
Steinar H. Gunderson ea9a328340 Merge commit 'c14b69790a62aad89fcc471cde482923dfe57f1e' into cluster
This is the last commit before there are more conflicts.
2025-12-24 15:17:55 +01:00
Steinar H. Gunderson fb9a3d3323 Merge commit '2dbb44e28d2e5b3c72ddbbd6f436d41f75031a22' into cluster 2025-12-24 15:17:18 +01:00
Steinar H. Gunderson 649004ed47 Merge commit 'db147fe2586527a854516016699949af53dc5b17' into cluster
This is the last commit before there are more conflicts.
2025-12-24 15:14:23 +01:00
Steinar H. Gunderson b7045f1bf6 Merge commit 'bc45cbc820a53a9fc405c06ca67bd7be3970344e' into cluster 2025-12-24 15:13:28 +01:00
Steinar H. Gunderson 5f5f991780 Merge commit '3502c8ae426506453ca64e87e48d962b327c2356' into cluster 2025-12-24 15:12:37 +01:00
Steinar H. Gunderson ba47c8eeeb Merge commit 'ddd250b9d655117920dd65a973cea2f8b3c57fce' into cluster 2025-12-24 15:03:08 +01:00
Steinar H. Gunderson 6b6279098d Merge commit 'd47aa639bd614b37a59f87e6ab68496580f0cf3e' into cluster
This is the last commit before there are more conflicts.
2025-12-24 15:02:03 +01:00
Steinar H. Gunderson 2a1ed1267e Merge commit 'd3fc1d835e5144cc98d6a7658fb8cfd9370792f1' into cluster 2025-12-24 14:37:41 +01:00
Steinar H. Gunderson 8e509e240a Merge commit '432995ad82119070afa0bf720eb65d800bcbf817' into cluster
This is the last commit before there are more conflicts.
2025-12-24 13:57:03 +01:00
Steinar H. Gunderson 1b80a18fac Merge commit '4912f5b0b5f2656bc5fcdb0af480765ad5aa8932' into cluster 2025-12-24 13:55:47 +01:00
Steinar H. Gunderson ee41dc5c98 Merge commit '14f6eab07d1d1e1a59372974e5534128676e9440' into cluster
This is the last commit before there are more conflicts.
2025-12-24 13:51:24 +01:00
Steinar H. Gunderson f0793a2080 Merge commit '9032c6cbe74ccf7e8963755501e7e6cc473ae471' into cluster
This is a rather involved merge, since the cluster changes interact
fairly badly with the refactoring we're merging:

The new code wants the search to know nothing at all about the UCI protocol,
it just calls a callback with some worker information (including a Position),
which then transforms that information into a InfoFull which is then sent
through another callback, upon which UCIEngine converts it to UCI and writes
it to stdout.

On the other hand, the MPI information wants to exchange PVs between workers
at this point, and it wants to use the UCI string as the primary medium of
exchange. Position is a lot of work to serialize, so we choose a middle road;
we capture the InfoFull by switching out the callback, serializes it, does the
MPI exchange on that, unserializes it and then continues with the print callback
(if there is new information).
2025-12-24 13:50:14 +01:00
Steinar H. Gunderson a3cb287405 Merge commit '299707d2c2cbf1694bb21ed4a375b54ef35d719e' into cluster 2025-12-23 16:21:07 +01:00
Joost VandeVondeleandGitHub b4ac3d6b96 Merge pull request #5161 from Disservin/cluster
Merge SF master in the cluster branch
2024-04-10 22:18:16 +02:00
Disservin 6c19bec86e Merge branch 'master' into cluster 2024-04-10 18:46:26 +02:00
Joost VandeVondeleandGitHub 8c4ac26c8e Merge pull request #4661 from vondele/clusterMergeMaster17
Merge SF 16 in the cluster branch
2023-07-05 12:11:22 +02:00
Joost VandeVondele a2b24e0030 Merge branch 'sf16branch' into clusterMergeMaster17 2023-07-03 20:35:36 +02:00
Joost VandeVondeleandGitHub 66b4e7f080 Merge pull request #4349 from vondele/clusterMergeMaster16
Cluster: merge master up to SF15.1
2023-01-21 16:57:10 +01:00
Joost VandeVondele 04a0be956d Merge branch 'master' into clusterMergeMaster16 2022-12-04 16:43:04 +01:00
Joost VandeVondele f327096cfb Merge branch 'master' into clusterMergeMaster15
fix small merge conflicts, lightly tested:
Score of cluster vs master: 1 - 0 - 59  [0.508] 60
Elo difference: 5.8 +/- 11.2, LOS: 84.1 %, DrawRatio: 98.3 %
2022-10-30 16:12:47 +01:00
Joost VandeVondele e592dcb8e3 Merge branch 'master' into clusterMergeMaster14
closes https://github.com/official-stockfish/Stockfish/pull/3980
2022-04-16 08:31:47 +02:00
Joost VandeVondeleandGitHub 80eae02603 Merge pull request #3789 from vondele/fixNodeCount
[cluster] fix node count in bench command
2021-11-13 17:17:17 +01:00
Joost VandeVondele 0ad7de3182 [cluster] fix node count in bench command
bugfix use the cluster wide node count in bench for reporting

No function change
2021-11-13 17:14:27 +01:00
Joost VandeVondeleandGitHub e0b4dc24c8 Merge pull request #3771 from vondele/clusterMergeMaster13
Cluster: merge SF 14.1
2021-10-31 21:38:23 +01:00
Joost VandeVondele b79ec2e0b2 Merge branch 'master' into clusterMergeMaster13
brings the cluster branch to SF 14.1

Fixes minor conflicts

local testing cluster 4x4T vs master 4T, 10+0.1s, noob_3moves:

Score of cluster vs master: 6 - 0 - 94  [0.530] 100
Elo difference: 20.9 +/- 16.2, LOS: 99.3 %, DrawRatio: 94.0 %

No functional change
2021-10-31 16:17:24 +01:00
Stéphane Nicolet 43c887d367 Merge branch 'vondele-clusterMergeMaster12' into cluster 2021-03-17 11:22:02 +01:00
Joost VandeVondele 3a187b863b Merge branch 'master' into clusterMergeMaster12
fixes minor merge conflicts

looks good:

Score of cluster vs master: 15 - 1 - 134  [0.547] 150
Elo difference: 32.5 +/- 17.5, LOS: 100.0 %, DrawRatio: 89.3 %

for 4 threads against 4x4 threads at 10+0.1s
2021-03-16 20:49:19 +01:00
Joost VandeVondele 5fcd0e6f2a Merge branch 'master' into clusterMergeMaster11
fixes minor conflicts.
2020-09-17 19:17:37 +02:00
Joost VandeVondele 19129473f2 Merge NNUE (master) in the cluster branch.
fixes minor merge conflicts, and a first quick testing looks OK:

4mpix4th vs 4th at 30+0.3s:

Score of cluster vs master: 3 - 0 - 37  [0.537] 40
Elo difference: 26.1 +/- 28.5, LOS: 95.8 %, DrawRatio: 92.5 %

No functional change.
2020-08-19 23:30:37 +02:00
Joost VandeVondele b706b91bb1 Update cluster branch to latest master
Fixes a few merge conflicts.

Verified equal bench for 1 rank, and expected performance master vs cluster with 2 ranks.

Score of cluster vs master: 196 - 54 - 400  [0.609] 650
Elo difference: 77.1 +/- 16.3, LOS: 100.0 %, DrawRatio: 61.5 %

No functional change.
2020-06-28 13:04:21 +02:00
Joost VandeVondele 8ec5faa46e Merge branch 'master' into clusterMergeMaster8 2020-01-18 08:26:43 +01:00
Joost VandeVondele 8a9d269855 Merge remote-tracking branch 'upstream/master' into clusterMergeMaster7 2019-10-20 09:20:34 +02:00
Joost VandeVondele 0b3c13107a Merge branch 'master' into clusterMergeMaster6 2019-07-11 15:26:46 +02:00
Joost VandeVondele 669074672c Some doc changes. No functional change. 2019-07-05 14:47:33 +02:00
Joost VandeVondele 0fd0e4e849 Merge branch 'master' into clusterMergeMaster6 2019-07-01 16:36:58 +02:00
Joost VandeVondele 85327828c9 Merge branch 'master' into clusterMergeMaster5 2019-05-01 08:23:22 +02:00
Joost VandeVondele 4cdb6386d8 Merge branch 'master' into clusterMergeMaster5 2019-03-21 07:13:00 +01:00
Joost VandeVondele 982880bd70 Merge remote-tracking branch 'upstream/master' into clusterMergeMaster4 2019-02-17 13:18:08 +01:00
Joost VandeVondeleandStéphane Nicolet bf17a410ec [Cluster] Use a sendrecv ring instead of allgather
Using point to point instead of a collective improves performance, and might be more flexible for future improvements.
Also corrects the condition for the number elements required to fill the send buffer.

The actual Elo gains depends a bit on the setup used for testing.

8mpi x 32t yields 141 - 102 - 957 ~ 11 Elo
8mpi x 1t yields 70 +- 9 Elo.
2019-01-24 10:39:24 +01:00
Joost VandeVondeleandStéphane Nicolet 5e7777e9d0 [Cluster] adds missing line
one-liner fixes a merge error, resulting in a garbage output line. No influence on play.
2019-01-17 08:06:25 +01:00
Joost VandeVondeleandStéphane Nicolet 10a920d7d7 [cluster] Improve user documentation
- add cluster info line
- provides basic info on positions received/stored in a cluster run,
  useful to judge performance.
- document most cluster functionality in the readme.md

No functional change
2019-01-14 09:11:33 +01:00
Joost VandeVondele 21819b7bf8 Merge branch 'master' into clusterMergeMaster3 2019-01-09 21:52:30 +01:00
Joost VandeVondeleandStéphane Nicolet 8c4338ae49 [Cluster] Param tweak.
Small tweak of parameters, yielding some Elo.

The cluster branch can now be considered to be in good shape. In local testing, it runs stable for >30k games. Performance benefits from an MPI implementation that is able to make asynchronous progress. The code should be run with 1 MPI rank per node, and threaded on the node.

Performance against master has now been measured. Master has been given 1 node with 32 cores/threads in standard SMP, the cluster branch has been given N=2..20 of those nodes, running the corresponding number of MPI processes, each with 32 threads. Time control has been 10s+0.1s, Hash 8MB/core, the book 8moves_v3.pgn, the number of games 400.

```
Score of cluster-2mpix32t vs master-32t: 96 - 27 - 277  [0.586] 400
Elo difference: 60.54 +/- 18.49

Score of cluster-3mpix32t vs master-32t: 101 - 18 - 281  [0.604] 400
Elo difference: 73.16 +/- 17.94

Score of cluster-4mpix32t vs master-32t: 126 - 18 - 256  [0.635] 400
Elo difference: 96.19 +/- 19.68

Score of cluster-5mpix32t vs master-32t: 110 - 5 - 285  [0.631] 400
Elo difference: 93.39 +/- 17.09

Score of cluster-6mpix32t vs master-32t: 117 - 9 - 274  [0.635] 400
Elo difference: 96.19 +/- 18.06

Score of cluster-7mpix32t vs master-32t: 142 - 10 - 248  [0.665] 400
Elo difference: 119.11 +/- 19.89

Score of cluster-8mpix32t vs master-32t: 125 - 14 - 261  [0.639] 400
Elo difference: 99.01 +/- 19.18

Score of cluster-9mpix32t vs master-32t: 137 - 7 - 256  [0.662] 400
Elo difference: 117.16 +/- 19.20

Score of cluster-10mpix32t vs master-32t: 145 - 8 - 247  [0.671] 400
Elo difference: 124.01 +/- 19.86

Score of cluster-16mpix32t vs master-32t: 153 - 6 - 241  [0.684] 400
Elo difference: 133.95 +/- 20.17

Score of cluster-20mpix32t vs master-32t: 134 - 8 - 258  [0.657] 400
Elo difference: 113.29 +/- 19.11
```

As the cluster parallelism is essentially lazyMPI, the nodes per second has been verified to scale perfectly to large node counts. Unfortunately, that is not necessarily indicative of playing strength. In the following 2min search from startPos, we reach about 4.8Gnps (128 nodes).

```
info depth 38 seldepth 51 multipv 1 score cp 53 nodes 576165794092 nps 4801341606 hashfull 1000 tbhits 0 time 120001 pv e2e4 c7c5 g1f3 d7d6 f1b5 c8d7 b5d7 d8d7 c2c4 b8c6 b1c3 g8f6 d2d4 d7g4 d4d5 c6d4 f3d4 g4d1 e1d1 c5d4 c3b5 a8c8 b2b3 a7a6 b5d4 f6e4 d1e2 g7g6 c1e3 f8g7 a1c1 e4c5 f2f3 f7f5 h1d1 e8g8 d4c2 c5d7 a2a4 a6a5 e3d4 f5f4 d4f2 f8f7 h2h3 d7c5
```
2019-01-06 15:38:31 +01:00
Joost VandeVondeleandStéphane Nicolet 8a3f8e21ae [Cluster] Move IO to the root.
Fixes one TODO, by moving the IO related to bestmove to the root, even if this move is found by a different rank.

This is needed to make sure IO from different ranks is ordered properly. If this is not done it is possible that e.g. a bestmove arrives before all info lines have been received, leading to output that confuses tools and humans alike (see e.g. https://github.com/cutechess/cutechess/issues/472)
2019-01-04 14:56:04 +01:00
Joost VandeVondeleandStéphane Nicolet 267ca781cd Always wait before posting the next call in _sync. 2019-01-02 11:16:24 +01:00
Joost VandeVondeleandStéphane Nicolet ac43bef5c5 [Cluster] Improve message passing part.
This rewrites in part the message passing part, using in place gather, and collecting, rather than merging, the data of all threads.

neutral with a single thread per rank:
Score of new-2mpi-1t vs old-2mpi-1t: 789 - 787 - 2615  [0.500] 4191
Elo difference: 0.17 +/- 6.44

likely progress with multiple threads per rank:
Score of new-2mpi-36t vs old-2mpi-36t: 76 - 53 - 471  [0.519] 600
Elo difference: 13.32 +/- 12.85
2019-01-02 11:16:24 +01:00
Joost VandeVondeleandStéphane Nicolet 7a32d26d5f [cluster] keep track of TB hits cluster-wide. 2018-12-29 15:34:57 +01:00
Joost VandeVondeleandStéphane Nicolet fb5c1f5bf5 Fix comment 2018-12-29 15:34:57 +01:00
Joost VandeVondeleandStéphane Nicolet 87f0fa55a0 [cluster] keep track of node counts cluster-wide.
This generalizes exchange of signals between the ranks using a non-blocking all-reduce. It is now used for the stop signal and the node count, but should be easily generalizable (TB hits, and ponder still missing). It avoids having long-lived outstanding non-blocking collectives (removes an early posted Ibarrier). A bit too short a test, but not worse than before:

Score of new-r4-1t vs old-r4-1t: 459 - 401 - 1505  [0.512] 2365
Elo difference: 8.52 +/- 8.43
2018-12-29 15:34:57 +01:00
Joost VandeVondeleandStéphane Nicolet 2f882309d5 fixup 2018-12-29 15:34:57 +01:00
Joost VandeVondeleandStéphane Nicolet 86953b9392 [cluster] Fix non-mpi compile
fix compile of the cluster branch in the non-mpi case.

Add a TODO as a reminder for the new voting scheme.

No functional changes
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet ba1c639836 [cluster] fill sendbuffer better
use a counter to track available elements.

Some elo gain, on 4 ranks:

Score of old-r4-1t vs new-r4-1t: 422 - 508 - 1694  [0.484] 2624
Elo difference: -11.39 +/- 7.90
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet e526c5aa52 [cluster] Make bench compatible
Fix one TODO.

Takes care of output from bench.
Sum nodes over ranks.
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet 9cd2c817db Add one more TODO 2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet 54a0a228f6 [cluster] Some formatting cleanup
standarize whitespace a bit.
Also adds two TODOs for follow up work.

No functional change.
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet 1cd2c7861a [cluster] avoid creating MPI data type.
there is no need to make an MPI data type for the sendbuffer, simpler and faster.

No functional change
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet 7af3f4da7a [cluster] Avoid TT saving our own TT entries.
avoid saving to TT the part of the receive buffer that actually originates from the same rank.

Now, on 1 mpi rank, we have the same bench as the non-mpi code on 1 thread.
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet 271181bb31 [cluster] Add depth condition to cluster TT saves.
since the logic for saving moves in the sendbuffer and the associated rehashing is expensive, only do it for TT stores of sufficient depth.

quite some gain in local testing with 4 ranks against the previous version.
Elo difference: 288.84 +/- 21.98

This starts to make the branch useful, but for on-node runs, difference remains to the standard threading.
2018-12-29 15:34:56 +01:00
noobpwnftwandStéphane Nicolet 66b2c6b9f1 Implement best move voting system for cluster
This implements the cluster version of https://github.com/official-stockfish/Stockfish/commit/d96c1c32a2fa109e7cc6cd07f6029cd13977121e
2018-12-29 15:34:56 +01:00
Joost VandeVondeleandStéphane Nicolet 2559c20c6e [cluster] Fix oversight in TT key reuse
In the original code, the position key stored in the TT is used to probe&store TT entries after message passing. Since we only store part of the bits in the TT, this leads to incorrect rehashing. This is fixed in this patch storing also the full key in the send buffers, and using that for hashing after message arrival.

Short testing with 4 ranks (old vs new) shows this is effective:
Score of mpiold vs mpinew: 84 - 275 - 265  [0.347] 624
Elo difference: -109.87 +/- 20.88
2018-12-29 15:34:55 +01:00
Joost VandeVondeleandStéphane Nicolet 2659c407c4 Fix segfault.
the wrong data type was passed to an MPI call, leading to occasional segfaults. This patch fixes this.

No functional change.
2018-12-29 15:34:55 +01:00
noobpwnftwandStéphane Nicolet 3730ae1efb Small simplifications and code cleanup
Non-functional simplifications.
2018-12-29 15:34:55 +01:00
noobpwnftwandStéphane Nicolet 0d6cdc0c6d Implement yielding loop while waiting for input
Some MPI implementations use busy-wait pooling, which will turn MPI_Bcast into busy-wait loop, workaround with our own yielding loop.
2018-12-29 15:34:55 +01:00
noobpwnftwandStéphane Nicolet 80afeb0d3b Fix consistency between PV and bestmove output
In case that a non-root mainThread on a node is the new best thread in the cluster, it should always output its PV.
2018-12-29 15:34:55 +01:00
noobpwnftwandStéphane Nicolet 2405b38165 Fix search result aggregation
This reverts my earlier change that only the root node gets to output best move after fixing problem with MPI_Allreduce by our custom operator(BestMoveOp). This function is not commutable and we must ensure that its output is consistent among all nodes.
2018-12-29 15:34:55 +01:00
noobpwnftwandStéphane Nicolet 8a95d269eb Implement proper stop signalling from root node
Previous behavior was to wait on all nodes to finish their search on their own TM and aggregate to root node via a blocking MPI_Allreduce call. This seems to be problematic.

In this commit a proper non-blocking signalling barrier was implemented to use TM from root node to control the cluster search, and disable TM on all non-root nodes.

Also includes some cosmetic fix to the nodes/NPS display.
2018-12-29 15:34:55 +01:00
noobpwnftwandStéphane Nicolet 3b7b632aa5 Fix a bug of outputting multiple lines of bestmove 2018-12-29 15:34:55 +01:00
Omri MorandStéphane Nicolet 29c166a072 MPI/Cluster implementation for Stockfish
Based on Peter Österlund's "Lazy Cluster" algorithm,
but with some simplifications.
To compile, point COMPCXX to the MPI C++ compiler wrapper (mpicxx).
2018-12-29 15:34:55 +01:00
17 changed files with 970 additions and 80 deletions
+27
View File
@@ -59,6 +59,33 @@ This distribution of Stockfish consists of the following files:
* a file with the .nnue extension, storing the neural network for the NNUE
evaluation. Binary distributions will have this file embedded.
## Stockfish on distributed memory systems
The cluster branch allows for running Stockfish on a cluster of servers (nodes)
that are connected with a high-speed and low-latency network, using the message
passing interface (MPI). In this case, one MPI process should be run per node,
and UCI options can be used to set the number of threads/hash per node as usual.
Typically, the engine will be invoked as
```
mpirun -np N /path/to/stockfish
```
where ```N``` stands for the number of MPI processes used (alternatives to ```mpirun```,
include ```mpiexec```, ```srun```). Use 1 mpi rank per node, and employ threading
according to the cores per node. To build the cluster
branch, it is sufficient to specify ```COMPCXX=mpicxx``` (or e.g. CC depending on the name
of the compiler providing MPI support) on the make command line, and do a clean build:
```
make -j ARCH=x86-64-modern clean build COMPCXX=mpicxx mpi=yes
```
Make sure that the MPI installation is configured to support ```MPI_THREAD_MULTIPLE```,
this might require adding system specific compiler options to the Makefile. Stockfish employs
non-blocking (asynchronous) communication, and benefits from an MPI
implementation that efficiently supports this. Some MPI implentations might benefit
from leaving 1 core/thread free for these asynchronous communications, and might require
setting additional environment variables. ```mpirun``` should forward stdin/stdout
to ```rank 0``` only (e.g. ```srun --input=0 --output=0```).
Refer to your MPI documentation for more info.
## Contributing
__See [Contributing Guide](CONTRIBUTING.md).__
+15 -2
View File
@@ -68,7 +68,7 @@ PGOBENCH = $(RUN_PREFIX) ./$(EXE) bench
### Source and object files
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
misc.cpp movegen.cpp movepick.cpp position.cpp \
misc.cpp movegen.cpp movepick.cpp position.cpp cluster.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
@@ -80,7 +80,8 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.
nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \
nnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \
position.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h \
cluster.h
OBJS = $(notdir $(SRCS:.cpp=.o))
@@ -121,6 +122,7 @@ VPATH = syzygy:nnue:nnue/features
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
# lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension
# lasx = yes/no --- -mlasx --- use Loongson Advanced SIMD eXtension
# mpi = yes/no --- -DUSE_MPI --- Use Message Passing Interface
#
# Note that Makefile is space sensitive, so when adding new architectures
# or modifying existing flags, you have to make sure there are no extra spaces
@@ -173,6 +175,7 @@ avx512icl = no
altivec = no
vsx = no
neon = no
mpi = no
dotprod = no
arm_version = 0
lsx = no
@@ -892,6 +895,15 @@ ifeq ($(OS), Android)
LDFLAGS += -fPIE -pie
endif
### 3.10 MPI
ifneq (,$(findstring mpi, $(CXX)))
mpi = yes
endif
ifeq ($(mpi),yes)
CXXFLAGS += -DUSE_MPI -Wno-cast-qual -fexceptions
DEPENDFLAGS += -DUSE_MPI
endif
### ==========================================================================
### Section 4. Public Targets
### ==========================================================================
@@ -1066,6 +1078,7 @@ config-sanity: net
echo "altivec: '$(altivec)'" && \
echo "vsx: '$(vsx)'" && \
echo "neon: '$(neon)'" && \
echo "mpi: '$(mpi)'" && \
echo "dotprod: '$(dotprod)'" && \
echo "arm_version: '$(arm_version)'" && \
echo "lsx: '$(lsx)'" && \
+490
View File
@@ -0,0 +1,490 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_MPI
#include <array>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <istream>
#include <map>
#include <mpi.h>
#include <string>
#include <vector>
#include "cluster.h"
#include "thread.h"
#include "timeman.h"
#include "tt.h"
#include "search.h"
namespace Stockfish {
namespace Distributed {
// Total number of ranks and rank within the communicator
static int world_rank = MPI_PROC_NULL;
static int world_size = 0;
// Signals between ranks exchange basic info using a dedicated communicator
static MPI_Comm signalsComm = MPI_COMM_NULL;
static MPI_Request reqSignals = MPI_REQUEST_NULL;
static uint64_t signalsCallCounter = 0;
// Signals are the number of nodes searched, stop, table base hits, transposition table saves
enum Signals : int {
SIG_NODES = 0,
SIG_STOP = 1,
SIG_TB = 2,
SIG_TTS = 3,
SIG_NB = 4
};
static uint64_t signalsSend[SIG_NB] = {};
static uint64_t signalsRecv[SIG_NB] = {};
static uint64_t nodesSearchedOthers = 0;
static uint64_t tbHitsOthers = 0;
static uint64_t TTsavesOthers = 0;
static uint64_t stopSignalsPosted = 0;
// The UCI threads of each rank exchange use a dedicated communicator
static MPI_Comm InputComm = MPI_COMM_NULL;
// bestMove requires MoveInfo communicators and data types
static MPI_Comm MoveComm = MPI_COMM_NULL;
static MPI_Datatype MIDatatype = MPI_DATATYPE_NULL;
// TT entries are communicated with a dedicated communicator.
// The receive buffer is used to gather information from all ranks.
// THe TTCacheCounter tracks the number of local elements that are ready to be sent.
static MPI_Comm TTComm = MPI_COMM_NULL;
static std::array<std::vector<KeyedTTEntry>, 2> TTSendRecvBuffs;
static std::array<MPI_Request, 2> reqsTTSendRecv = {MPI_REQUEST_NULL, MPI_REQUEST_NULL};
static uint64_t sendRecvPosted = 0;
static std::atomic<uint64_t> TTCacheCounter = {};
/// Initialize MPI and associated data types. Note that the MPI library must be configured
/// to support MPI_THREAD_MULTIPLE, since multiple threads access MPI simultaneously.
void init() {
int thread_support;
MPI_Init_thread(nullptr, nullptr, MPI_THREAD_MULTIPLE, &thread_support);
if (thread_support < MPI_THREAD_MULTIPLE)
{
std::cerr << "Stockfish requires support for MPI_THREAD_MULTIPLE." << std::endl;
std::exit(EXIT_FAILURE);
}
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
const std::array<MPI_Aint, 5> MIdisps = {offsetof(MoveInfo, move), offsetof(MoveInfo, ponder),
offsetof(MoveInfo, depth), offsetof(MoveInfo, score),
offsetof(MoveInfo, rank)};
MPI_Type_create_hindexed_block(5, 1, MIdisps.data(), MPI_INT, &MIDatatype);
MPI_Type_commit(&MIDatatype);
MPI_Comm_dup(MPI_COMM_WORLD, &InputComm);
MPI_Comm_dup(MPI_COMM_WORLD, &TTComm);
MPI_Comm_dup(MPI_COMM_WORLD, &MoveComm);
MPI_Comm_dup(MPI_COMM_WORLD, &signalsComm);
}
/// Finalize MPI and free the associated data types.
void finalize() {
MPI_Type_free(&MIDatatype);
MPI_Comm_free(&InputComm);
MPI_Comm_free(&TTComm);
MPI_Comm_free(&MoveComm);
MPI_Comm_free(&signalsComm);
MPI_Finalize();
}
/// Return the total number of ranks
int size() { return world_size; }
/// Return the rank (index) of the process
int rank() { return world_rank; }
/// The receive buffer depends on the number of MPI ranks and threads, resize as needed
void ttSendRecvBuff_resize(size_t nThreads) {
for (int i : {0, 1})
{
TTSendRecvBuffs[i].resize(TTCacheSize * world_size * nThreads);
std::fill(TTSendRecvBuffs[i].begin(), TTSendRecvBuffs[i].end(), KeyedTTEntry());
}
}
/// As input is only received by the root (rank 0) of the cluster, this input must be relayed
/// to the UCI threads of all ranks, in order to setup the position, etc. We do this with a
/// dedicated getline implementation, where the root broadcasts to all other ranks the received
/// information.
bool getline(std::istream& input, std::string& str) {
int size;
std::vector<char> vec;
int state;
if (is_root())
{
state = static_cast<bool>(std::getline(input, str));
vec.assign(str.begin(), str.end());
size = vec.size();
}
// Some MPI implementations use busy-wait polling, while we need yielding as otherwise
// the UCI thread on the non-root ranks would be consuming resources.
static MPI_Request reqInput = MPI_REQUEST_NULL;
MPI_Ibcast(&size, 1, MPI_INT, 0, InputComm, &reqInput);
if (is_root())
MPI_Wait(&reqInput, MPI_STATUS_IGNORE);
else
{
while (true)
{
int flag;
MPI_Test(&reqInput, &flag, MPI_STATUS_IGNORE);
if (flag)
break;
else
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
// Broadcast received string
if (!is_root())
vec.resize(size);
MPI_Bcast(vec.data(), size, MPI_CHAR, 0, InputComm);
if (!is_root())
str.assign(vec.begin(), vec.end());
MPI_Bcast(&state, 1, MPI_INT, 0, InputComm);
return state;
}
/// Sending part of the signal communication loop
namespace {
void signals_send(const ThreadPool& threads) {
signalsSend[SIG_NODES] = threads.nodes_searched();
signalsSend[SIG_TB] = threads.tb_hits();
signalsSend[SIG_TTS] = threads.TT_saves();
signalsSend[SIG_STOP] = threads.stop;
MPI_Iallreduce(signalsSend, signalsRecv, SIG_NB, MPI_UINT64_T, MPI_SUM, signalsComm,
&reqSignals);
++signalsCallCounter;
}
/// Processing part of the signal communication loop.
/// For some counters (e.g. nodes) we only keep their sum on the other nodes
/// allowing to add local counters at any time for more fine grained process,
/// which is useful to indicate progress during early iterations, and to have
/// node counts that exactly match the non-MPI code in the single rank case.
/// This call also propagates the stop signal between ranks.
void signals_process(ThreadPool& threads) {
nodesSearchedOthers = signalsRecv[SIG_NODES] - signalsSend[SIG_NODES];
tbHitsOthers = signalsRecv[SIG_TB] - signalsSend[SIG_TB];
TTsavesOthers = signalsRecv[SIG_TTS] - signalsSend[SIG_TTS];
stopSignalsPosted = signalsRecv[SIG_STOP];
if (signalsRecv[SIG_STOP] > 0)
threads.stop = true;
}
void sendrecv_post() {
++sendRecvPosted;
MPI_Irecv(TTSendRecvBuffs[sendRecvPosted % 2].data(),
TTSendRecvBuffs[sendRecvPosted % 2].size() * sizeof(KeyedTTEntry), MPI_BYTE,
(rank() + size() - 1) % size(), 42, TTComm, &reqsTTSendRecv[0]);
MPI_Isend(TTSendRecvBuffs[(sendRecvPosted + 1) % 2].data(),
TTSendRecvBuffs[(sendRecvPosted + 1) % 2].size() * sizeof(KeyedTTEntry), MPI_BYTE,
(rank() + 1) % size(), 42, TTComm, &reqsTTSendRecv[1]);
}
}
/// During search, most message passing is asynchronous, but at the end of
/// search it makes sense to bring them to a common, finalized state.
void signals_sync(ThreadPool& threads) {
while (stopSignalsPosted < uint64_t(size()))
signals_poll(threads);
// Finalize outstanding messages of the signal loops.
// We might have issued one call less than needed on some ranks.
uint64_t globalCounter;
MPI_Allreduce(&signalsCallCounter, &globalCounter, 1, MPI_UINT64_T, MPI_MAX, MoveComm);
if (signalsCallCounter < globalCounter)
{
MPI_Wait(&reqSignals, MPI_STATUS_IGNORE);
signals_send(threads);
}
assert(signalsCallCounter == globalCounter);
MPI_Wait(&reqSignals, MPI_STATUS_IGNORE);
signals_process(threads);
// Finalize outstanding messages in the sendRecv loop
MPI_Allreduce(&sendRecvPosted, &globalCounter, 1, MPI_UINT64_T, MPI_MAX, MoveComm);
while (sendRecvPosted < globalCounter)
{
MPI_Waitall(reqsTTSendRecv.size(), reqsTTSendRecv.data(), MPI_STATUSES_IGNORE);
sendrecv_post();
}
assert(sendRecvPosted == globalCounter);
MPI_Waitall(reqsTTSendRecv.size(), reqsTTSendRecv.data(), MPI_STATUSES_IGNORE);
}
/// Initialize signal counters to zero.
void signals_init() {
stopSignalsPosted = tbHitsOthers = TTsavesOthers = nodesSearchedOthers = 0;
signalsSend[SIG_NODES] = signalsRecv[SIG_NODES] = 0;
signalsSend[SIG_TB] = signalsRecv[SIG_TB] = 0;
signalsSend[SIG_TTS] = signalsRecv[SIG_TTS] = 0;
signalsSend[SIG_STOP] = signalsRecv[SIG_STOP] = 0;
}
/// Poll the signal loop, and start next round as needed.
void signals_poll(ThreadPool& threads) {
int flag;
MPI_Test(&reqSignals, &flag, MPI_STATUS_IGNORE);
if (flag)
{
signals_process(threads);
signals_send(threads);
}
}
/// Provide basic info related the cluster performance, in particular, the number of signals send,
/// signals per sounds (sps), the number of gathers, the number of positions gathered (per node and per second, gpps)
/// The number of TT saves and TT saves per second. If gpps equals approximately TTSavesps the gather loop has enough bandwidth.
void cluster_info(const ThreadPool& threads, Depth depth, TimePoint elapsed) {
// TimePoint elapsed = Time.elapsed() + 1;
uint64_t TTSaves = TT_saves(threads);
sync_cout << "info depth " << depth << " cluster "
<< " signals " << signalsCallCounter << " sps " << signalsCallCounter * 1000 / elapsed
<< " sendRecvs " << sendRecvPosted << " srpps "
<< TTSendRecvBuffs[0].size() * sendRecvPosted * 1000 / elapsed << " TTSaves "
<< TTSaves << " TTSavesps " << TTSaves * 1000 / elapsed << sync_endl;
}
/// When a TT entry is saved, additional steps are taken if the entry is of sufficient depth.
/// If sufficient entries has been collected, a communication is initiated.
/// If a communication has been completed, the received results are saved to the TT.
void save(TranspositionTable& TT,
ThreadPool& threads,
Search::Worker* thread,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
Bound b,
Depth d,
Move m,
Value ev,
uint8_t generation8) {
// Standard save to the TT
ttWriter.write(k, v, PvHit, b, d, m, ev, generation8);
// If the entry is of sufficient depth to be worth communicating, take action.
if (d > 3)
{
// count the TTsaves to information: this should be relatively similar
// to the number of entries we can send/recv.
thread->TTsaves.fetch_add(1, std::memory_order_relaxed);
// Add to thread's send buffer, the locking here avoids races when the master thread
// prepares the send buffer.
{
std::lock_guard<std::mutex> lk(thread->ttCache.mutex);
thread->ttCache.buffer.replace(KeyedTTEntry(k, TTData(m, v, ev, d, b, PvHit)));
++TTCacheCounter;
}
size_t recvBuffPerRankSize = threads.size() * TTCacheSize;
// Communicate on main search thread, as soon the threads combined have collected
// sufficient data to fill the send buffers.
if (thread == threads.main_thread()->worker.get() && TTCacheCounter > recvBuffPerRankSize)
{
// Test communication status
int flag;
MPI_Testall(reqsTTSendRecv.size(), reqsTTSendRecv.data(), &flag, MPI_STATUSES_IGNORE);
// Current communication is complete
if (flag)
{
// Save all received entries to TT, and store our TTCaches, ready for the next round of communication
for (size_t irank = 0; irank < size_t(size()); ++irank)
{
if (irank
== size_t(
rank())) // this is our part, fill the part of the buffer for sending
{
// Copy from the thread caches to the right spot in the buffer
size_t i = irank * recvBuffPerRankSize;
for (auto&& th : threads)
{
std::lock_guard<std::mutex> lk(th->worker->ttCache.mutex);
for (auto&& e : th->worker->ttCache.buffer)
TTSendRecvBuffs[sendRecvPosted % 2][i++] = e;
// Reset thread's send buffer
th->worker->ttCache.buffer = {};
}
TTCacheCounter = 0;
}
else // process data received from the corresponding rank.
for (size_t i = irank * recvBuffPerRankSize;
i < (irank + 1) * recvBuffPerRankSize; ++i)
{
auto&& e = TTSendRecvBuffs[sendRecvPosted % 2][i];
auto [ttHit, ttData, ttWriterForRecvd] = TT.probe(e.first);
ttWriterForRecvd.write(e.first, e.second.value, e.second.is_pv,
e.second.bound, e.second.depth, e.second.move,
e.second.eval, TT.generation());
}
}
// Start next communication
sendrecv_post();
// Force check of time on the next occasion, the above actions might have taken some time.
thread->main_manager()->callsCnt = 0;
}
}
}
}
/// Picks the bestMove across ranks, and send the associated info and PV to the root of the cluster.
/// Note that this bestMove and PV must be output by the root, the guarantee proper ordering of output.
/// TODO update to the scheme in master.. can this use aggregation of votes?
void pick_moves(MoveInfo& mi, std::vector<std::vector<char>>& serializedInfo) {
MoveInfo* pMoveInfo = NULL;
if (is_root())
{
pMoveInfo = (MoveInfo*) malloc(sizeof(MoveInfo) * size());
}
MPI_Gather(&mi, 1, MIDatatype, pMoveInfo, 1, MIDatatype, 0, MoveComm);
if (is_root())
{
std::map<int, int> votes;
int minScore = pMoveInfo[0].score;
for (int i = 0; i < size(); ++i)
{
minScore = std::min(minScore, pMoveInfo[i].score);
votes[pMoveInfo[i].move] = 0;
}
for (int i = 0; i < size(); ++i)
{
votes[pMoveInfo[i].move] += pMoveInfo[i].score - minScore + pMoveInfo[i].depth;
}
int bestVote = votes[pMoveInfo[0].move];
for (int i = 0; i < size(); ++i)
{
if (votes[pMoveInfo[i].move] > bestVote)
{
bestVote = votes[pMoveInfo[i].move];
mi = pMoveInfo[i];
}
}
free(pMoveInfo);
}
// Send around the final result
MPI_Bcast(&mi, 1, MIDatatype, 0, MoveComm);
// Send PV line to root as needed
if (mi.rank != 0 && mi.rank == rank())
{
int numLines = serializedInfo.size();
MPI_Send(&numLines, 1, MPI_INT, 0, 42, MoveComm);
for (const auto& serializedInfoOne : serializedInfo)
{
int size;
size = serializedInfoOne.size();
MPI_Send(&size, 1, MPI_INT, 0, 42, MoveComm);
MPI_Send(serializedInfoOne.data(), size, MPI_CHAR, 0, 42, MoveComm);
}
}
if (mi.rank != 0 && is_root())
{
serializedInfo.clear();
int numLines;
MPI_Recv(&numLines, 1, MPI_INT, mi.rank, 42, MoveComm, MPI_STATUS_IGNORE);
for (int i = 0; i < numLines; ++i)
{
int size;
std::vector<char> vec;
MPI_Recv(&size, 1, MPI_INT, mi.rank, 42, MoveComm, MPI_STATUS_IGNORE);
vec.resize(size);
MPI_Recv(vec.data(), size, MPI_CHAR, mi.rank, 42, MoveComm, MPI_STATUS_IGNORE);
serializedInfo.push_back(std::move(vec));
}
}
}
/// Return nodes searched (lazily updated cluster wide in the signal loop)
uint64_t nodes_searched(const ThreadPool& threads) {
return nodesSearchedOthers + threads.nodes_searched();
}
/// Return table base hits (lazily updated cluster wide in the signal loop)
uint64_t tb_hits(const ThreadPool& threads) { return tbHitsOthers + threads.tb_hits(); }
/// Return the number of saves to the TT buffers, (lazily updated cluster wide in the signal loop)
uint64_t TT_saves(const ThreadPool& threads) { return TTsavesOthers + threads.TT_saves(); }
}
}
#else
#include "cluster.h"
#include "thread.h"
namespace Stockfish {
namespace Distributed {
uint64_t nodes_searched(const ThreadPool& threads) { return threads.nodes_searched(); }
uint64_t tb_hits(const ThreadPool& threads) { return threads.tb_hits(); }
uint64_t TT_saves(const ThreadPool& threads) { return threads.TT_saves(); }
}
}
#endif // USE_MPI
+158
View File
@@ -0,0 +1,158 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLUSTER_H_INCLUDED
#define CLUSTER_H_INCLUDED
#include <algorithm>
#include <array>
#include <istream>
#include <string>
#include "misc.h"
#include "tt.h"
namespace Stockfish {
class Thread;
class ThreadPool;
namespace Search {
class Worker;
}
/// The Distributed namespace contains functionality required to run on distributed
/// memory architectures using MPI as the message passing interface. On a high level,
/// a 'lazy SMP'-like scheme is implemented where TT saves of sufficient depth are
/// collected on each rank and distributed to, and used by, all other ranks,
/// which search essentially independently. The root (MPI rank 0) of the cluster
/// is responsible for all I/O and time management, communicating this info to
/// the other ranks as needed. UCI options such as Threads and Hash specify these
/// quantities per MPI rank. It is recommended to have one rank (MPI process) per node.
/// For the non-MPI case, wrappers that will be compiler-optimized away are provided.
namespace Distributed {
/// Basic info to find the cluster-wide bestMove
struct MoveInfo {
int move;
int ponder;
int depth;
int score;
int rank;
};
#ifdef USE_MPI
// store the TTData with its (full) key, so it can be saved on the receiver side
using KeyedTTEntry = std::pair<Key, TTData>;
constexpr std::size_t TTCacheSize = 16;
// Threads locally cache their high-depth TT entries till a batch can be send by MPI
template<std::size_t N>
class TTCache: public std::array<KeyedTTEntry, N> {
struct Compare {
inline bool operator()(const KeyedTTEntry& lhs, const KeyedTTEntry& rhs) {
return lhs.second.depth > rhs.second.depth;
}
};
Compare compare;
public:
// Keep a heap of entries replacing low depth with high depth entries
bool replace(const KeyedTTEntry& value) {
if (compare(value, this->front()))
{
std::pop_heap(this->begin(), this->end(), compare);
this->back() = value;
std::push_heap(this->begin(), this->end(), compare);
return true;
}
return false;
}
};
void init();
void finalize();
bool getline(std::istream& input, std::string& str);
int size();
int rank();
inline bool is_root() { return rank() == 0; }
void save(TranspositionTable&,
ThreadPool&,
Search::Worker* thread,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
Bound b,
Depth d,
Move m,
Value ev,
uint8_t generation8);
void pick_moves(MoveInfo& mi, std::vector<std::vector<char>>& PVLine);
void ttSendRecvBuff_resize(size_t nThreads);
uint64_t nodes_searched(const ThreadPool&);
uint64_t tb_hits(const ThreadPool&);
uint64_t TT_saves(const ThreadPool&);
void cluster_info(const ThreadPool&, Depth depth, TimePoint elapsed);
void signals_init();
void signals_poll(ThreadPool& threads);
void signals_sync(ThreadPool& threads);
#else
inline void init() {}
inline void finalize() {}
inline bool getline(std::istream& input, std::string& str) {
return static_cast<bool>(std::getline(input, str));
}
constexpr int size() { return 1; }
constexpr int rank() { return 0; }
constexpr bool is_root() { return true; }
inline void save(TranspositionTable&,
ThreadPool&,
Search::Worker*,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
Bound b,
Depth d,
Move m,
Value ev,
uint8_t generation8) {
ttWriter.write(k, v, PvHit, b, d, m, ev, generation8);
}
inline void pick_moves(MoveInfo&, std::vector<std::vector<char>>&) {}
inline void ttSendRecvBuff_resize(size_t) {}
uint64_t nodes_searched(const ThreadPool&);
uint64_t tb_hits(const ThreadPool&);
uint64_t TT_saves(const ThreadPool&);
inline void cluster_info(const ThreadPool&, Depth, TimePoint) {}
inline void signals_init() {}
inline void signals_poll(ThreadPool&) {}
inline void signals_sync(ThreadPool&) {}
#endif /* USE_MPI */
}
}
#endif // #ifndef CLUSTER_H_INCLUDED
+3
View File
@@ -251,6 +251,9 @@ void Engine::resize_threads() {
void Engine::set_tt_size(size_t mb) {
wait_for_search_finished();
tt.resize(mb, threads);
// Adjust cluster buffers
Distributed::ttSendRecvBuff_resize(threads.num_threads());
}
void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
+4
View File
@@ -28,6 +28,8 @@
using namespace Stockfish;
int main(int argc, char* argv[]) {
Distributed::init();
if (Distributed::is_root())
std::cout << engine_info() << std::endl;
Bitboards::init();
@@ -39,5 +41,7 @@ int main(int argc, char* argv[]) {
uci->loop();
Distributed::finalize();
return 0;
}
+4 -2
View File
@@ -28,6 +28,7 @@
#define INCBIN_SILENCE_BITCODE_WARNING
#include "../incbin/incbin.h"
#include "../cluster.h"
#include "../evaluate.h"
#include "../misc.h"
#include "../position.h"
@@ -221,11 +222,12 @@ void Network<Arch, Transformer>::verify(std::string
if (f)
{
size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;
if (Distributed::is_root())
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024))
+ "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", "
+ std::to_string(network[0].TransformedFeatureDimensions) + ", "
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
+ ", 1))");
+ std::to_string(network[0].FC_0_OUTPUTS) + ", "
+ std::to_string(network[0].FC_1_OUTPUTS) + ", 1))");
}
}
+2 -1
View File
@@ -21,6 +21,7 @@
#include <cstdint>
#include "cluster.h"
#include "movegen.h"
#include "position.h"
#include "types.h"
@@ -49,7 +50,7 @@ uint64_t perft(Position& pos, Depth depth) {
nodes += cnt;
pos.undo_move(m);
}
if (Root)
if (Root && Distributed::is_root())
sync_cout << UCIEngine::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
}
return nodes;
+155 -27
View File
@@ -34,6 +34,7 @@
#include <utility>
#include "bitboard.h"
#include "cluster.h"
#include "evaluate.h"
#include "history.h"
#include "misc.h"
@@ -198,6 +199,7 @@ void Search::Worker::start_searching() {
if (rootMoves.empty())
{
rootMoves.emplace_back(Move::none());
if (Distributed::is_root())
main_manager()->updates.onUpdateNoMoves(
{0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}});
}
@@ -213,19 +215,24 @@ void Search::Worker::start_searching() {
// GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
// until the GUI sends one of those commands.
while (!threads.stop && (main_manager()->ponder || limits.infinite))
{} // Busy wait for a stop or a ponder reset
{
Distributed::signals_poll(threads);
} // Busy wait for a stop or a ponder reset
// Stop the threads if not already stopped (also raise the stop if
// "ponderhit" just reset threads.ponder)
threads.stop = true;
// Signal and synchronize all other ranks
Distributed::signals_sync(threads);
// Wait until all threads have finished
threads.wait_for_search_finished();
// When playing in 'nodes as time' mode, subtract the searched nodes from
// the available ones before exiting.
if (limits.npmsec)
main_manager()->tm.advance_nodes_time(threads.nodes_searched()
main_manager()->tm.advance_nodes_time(Distributed::nodes_searched(threads)
- limits.inc[rootPos.side_to_move()]);
Worker* bestThread = this;
@@ -239,18 +246,53 @@ void Search::Worker::start_searching() {
main_manager()->bestPreviousScore = bestThread->rootMoves[0].score;
main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore;
// Send again PV info if we have a new best thread
if (bestThread != this)
// Temporarily switch out onUpdateFull to capture the PV information that we need,
// so that we can exchange it through MPI. (We may end up not actually printing
// it out.)
auto oldOnUpdateFull = std::move(main_manager()->updates.onUpdateFull);
std::vector<std::vector<char>> serializedInfo; // One for each MultiPV.
main_manager()->updates.onUpdateFull = [&](const InfoFull& info) {
serializedInfo.push_back(info.serialize());
};
main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth);
assert(!serializedInfo.empty());
main_manager()->updates.onUpdateFull = std::move(oldOnUpdateFull);
std::string ponder;
Move bestMove = bestThread->rootMoves[0].pv[0];
Move ponderMove = Move::none();
if (bestThread->rootMoves[0].pv.size() > 1
|| bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos))
ponder = UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
ponderMove = bestThread->rootMoves[0].pv[1];
// Exchange info as needed
Distributed::MoveInfo mi{bestMove.raw(), ponderMove.raw(), bestThread->completedDepth,
bestThread->rootMoves[0].score, Distributed::rank()};
Distributed::pick_moves(mi, serializedInfo);
main_manager()->bestPreviousScore = static_cast<Value>(mi.score);
if (Distributed::is_root())
{
// Send again PV info if we have a new best thread/rank
if (bestThread != this || mi.rank != 0)
{
for (const auto& serializedInfoOne : serializedInfo)
{
Search::InfoFull info = Search::InfoFull::unserialize(serializedInfoOne);
main_manager()->updates.onUpdateFull(info);
}
}
bestMove = static_cast<Move>(mi.move);
ponderMove = static_cast<Move>(mi.ponder);
std::string ponder;
if (ponderMove != Move::none())
ponder = UCIEngine::move(ponderMove, rootPos.is_chess960());
auto bestmove = UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
main_manager()->updates.onBestmove(bestmove, ponder);
}
}
// Main iterative deepening loop. It calls search()
@@ -320,7 +362,7 @@ void Search::Worker::iterative_deepening() {
// Iterative deepening loop until requested to stop or the target depth is reached
while (++rootDepth < MAX_PLY && !threads.stop
&& !(limits.depth && mainThread && rootDepth > limits.depth))
&& !(limits.depth && mainThread && Distributed::is_root() && rootDepth > limits.depth))
{
// Age out PV variability metric
if (mainThread)
@@ -391,9 +433,12 @@ void Search::Worker::iterative_deepening() {
// When failing high/low give some update before a re-search. To avoid
// excessive output that could hang GUIs like Fritz 19, only start
// at nodes > 10M (rather than depth N, which can be reached quickly)
if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta)
&& nodes > 10000000)
if (Distributed::is_root() && mainThread && multiPV == 1
&& (bestValue <= alpha || bestValue >= beta) && nodes > 10000000)
{
main_manager()->pv(*this, threads, tt, rootDepth);
Distributed::cluster_info(threads, rootDepth, elapsed());
}
// In case of failing low/high increase aspiration window and re-search,
// otherwise exit the loop.
@@ -423,7 +468,7 @@ void Search::Worker::iterative_deepening() {
// Sort the PV lines searched so far and update the GUI
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
if (mainThread
if (Distributed::is_root() && mainThread
&& (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000)
// A thread that aborted search can have mated-in/TB-loss PV and
// score that cannot be trusted, i.e. it can be delayed or refuted
@@ -431,7 +476,10 @@ void Search::Worker::iterative_deepening() {
// we suppress this output and below pick a proven score/PV for this
// thread (from the previous iteration).
&& !(threads.abortedSearch && is_loss(rootMoves[0].uciScore)))
{
main_manager()->pv(*this, threads, tt, rootDepth);
Distributed::cluster_info(threads, rootDepth, elapsed() + 1);
}
if (threads.stop)
break;
@@ -737,8 +785,8 @@ Value Search::Worker::search(
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);
// Static evaluation is saved as it was before adjustment by correction history
ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(),
unadjustedStaticEval, tt.generation());
Distributed::save(tt, threads, this, ttWriter, posKey, VALUE_NONE, ss->ttPv, BOUND_NONE,
DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, tt.generation());
}
// Set up the improving flag, which is true if current static evaluation is
@@ -835,9 +883,9 @@ Value Search::Worker::search(
if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha))
{
ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, b,
std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE,
tt.generation());
Distributed::save(
tt, threads, this, ttWriter, posKey, value_to_tt(value, ss->ply), ss->ttPv, b,
std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, tt.generation());
return value;
}
@@ -971,8 +1019,9 @@ Value Search::Worker::search(
if (value >= probCutBeta)
{
// Save ProbCut data into transposition table
ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER,
probCutDepth + 1, move, unadjustedStaticEval, tt.generation());
Distributed::save(tt, threads, this, ttWriter, posKey, value_to_tt(value, ss->ply),
ss->ttPv, BOUND_LOWER, probCutDepth + 1, move,
unadjustedStaticEval, tt.generation());
if (!is_decisive(value))
return value - (probCutBeta - beta);
@@ -1021,7 +1070,7 @@ moves_loop: // When in check, search starts here
ss->moveCount = ++moveCount;
if (rootNode && is_mainthread() && nodes > 10000000)
if (rootNode && Distributed::is_root() && is_mainthread() && nodes > 10000000)
{
main_manager()->updates.onIter(
{depth, UCIEngine::move(move, pos.is_chess960()), moveCount + pvIdx});
@@ -1464,7 +1513,8 @@ moves_loop: // When in check, search starts here
// Write gathered information in transposition table. Note that the
// static evaluation is saved as it was before correction history.
if (!excludedMove && !(rootNode && pvIdx))
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv,
Distributed::save(tt, threads, this, ttWriter, posKey, value_to_tt(bestValue, ss->ply),
ss->ttPv,
bestValue >= beta ? BOUND_LOWER
: PvNode && bestMove ? BOUND_EXACT
: BOUND_UPPER,
@@ -1593,9 +1643,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
bestValue = (bestValue + beta) / 2;
if (!ss->ttHit)
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
Distributed::save(tt, threads, this, ttWriter, posKey,
value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval,
tt.generation());
return bestValue;
}
@@ -1724,7 +1776,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
// Save gathered info in transposition table. The static evaluation
// is saved as it was before adjustment by correction history.
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit,
Distributed::save(tt, threads, this, ttWriter, posKey, value_to_tt(bestValue, ss->ply), pvHit,
bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, DEPTH_QS, bestMove,
unadjustedStaticEval, tt.generation());
@@ -1747,7 +1799,7 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {
// This function is intended for use only when printing PV outputs, and not used
// for making decisions within the search algorithm itself.
TimePoint Search::Worker::elapsed() const {
return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); });
return main_manager()->tm.elapsed([this]() { return Distributed::nodes_searched(threads); });
}
TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); }
@@ -1950,7 +2002,8 @@ void SearchManager::check_time(Search::Worker& worker) {
static TimePoint lastInfoTime = now();
TimePoint elapsed = tm.elapsed([&worker]() { return worker.threads.nodes_searched(); });
TimePoint elapsed =
tm.elapsed([&worker]() { return Distributed::nodes_searched(worker.threads); });
TimePoint tick = worker.limits.startTime + elapsed;
if (tick - lastInfoTime >= 1000)
@@ -1959,6 +2012,9 @@ void SearchManager::check_time(Search::Worker& worker) {
dbg_print();
}
// poll on MPI signals
Distributed::signals_poll(worker.threads);
// We should not stop pondering until told so by the GUI
if (ponder)
return;
@@ -1969,7 +2025,8 @@ void SearchManager::check_time(Search::Worker& worker) {
worker.completedDepth >= 1
&& ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit))
|| (worker.limits.movetime && elapsed >= worker.limits.movetime)
|| (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes)))
|| (worker.limits.nodes
&& Distributed::nodes_searched(worker.threads) >= worker.limits.nodes)))
worker.threads.stop = worker.threads.abortedSearch = true;
}
@@ -2116,12 +2173,13 @@ void SearchManager::pv(Search::Worker& worker,
const TranspositionTable& tt,
Depth depth) {
const auto nodes = threads.nodes_searched();
const auto nodes = Distributed::nodes_searched(threads);
auto& rootMoves = worker.rootMoves;
auto& pos = worker.rootPos;
size_t pvIdx = worker.pvIdx;
size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);
uint64_t tbHits =
Distributed::tb_hits(threads) + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);
for (size_t i = 0; i < multiPV; ++i)
{
@@ -2207,5 +2265,75 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po
return pv.size() > 1;
}
std::vector<char> Search::InfoFull::serialize() const {
std::vector<char> vec;
vec.resize(sizeof(*this) + 3 * sizeof(size_t) + wdl.size() + bound.size() + pv.size());
char* ptr = vec.data();
// The base struct.
memcpy(ptr, this, sizeof(*this));
ptr += sizeof(*this);
// All string lengths.
size_t wdl_len = wdl.size();
memcpy(ptr, &wdl_len, sizeof(wdl_len));
ptr += sizeof(wdl_len);
size_t bound_len = bound.size();
memcpy(ptr, &bound_len, sizeof(bound_len));
ptr += sizeof(bound_len);
size_t pv_len = pv.size();
memcpy(ptr, &pv_len, sizeof(pv_len));
ptr += sizeof(pv_len);
// The string data itself.
memcpy(ptr, wdl.data(), wdl_len);
ptr += wdl_len;
memcpy(ptr, bound.data(), bound_len);
ptr += bound_len;
memcpy(ptr, pv.data(), pv_len);
ptr += pv_len;
assert(ptr == vec.data() + vec.size());
return vec;
}
InfoFull Search::InfoFull::unserialize(const std::vector<char>& buf) {
InfoFull info;
const char* ptr = buf.data();
// The base struct.
memcpy(&info, ptr, sizeof(info));
ptr += sizeof(info);
// All string lengths.
size_t wdl_len;
memcpy(&wdl_len, ptr, sizeof(wdl_len));
ptr += sizeof(wdl_len);
size_t bound_len;
memcpy(&bound_len, ptr, sizeof(bound_len));
ptr += sizeof(bound_len);
size_t pv_len;
memcpy(&pv_len, ptr, sizeof(pv_len));
ptr += sizeof(pv_len);
// The string data itself.
info.wdl = std::string_view(ptr, wdl_len);
ptr += wdl_len;
info.bound = std::string_view(ptr, bound_len);
ptr += bound_len;
info.pv = std::string_view(ptr, pv_len);
ptr += pv_len;
assert(ptr == buf.data() + buf.size());
return info;
}
} // namespace Stockfish
+37 -3
View File
@@ -28,10 +28,12 @@
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <vector>
#include "cluster.h"
#include "history.h"
#include "misc.h"
#include "nnue/network.h"
@@ -121,7 +123,9 @@ struct LimitsType {
ponderMode = false;
}
bool use_time_management() const { return time[WHITE] || time[BLACK]; }
bool use_time_management() const {
return Distributed::is_root() && (time[WHITE] || time[BLACK]);
}
std::vector<std::string> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
@@ -178,6 +182,14 @@ struct InfoFull: InfoShort {
size_t tbHits;
std::string_view pv;
int hashfull;
std::vector<char> serialize() const;
// NOTE: The returned InfoFull will contain string_views that point
// into the given buffer, so it must live (and not be reallocated)
// (and not be reallocated) for at least as long as you want to
// access fields in the InfoFull.
static InfoFull unserialize(const std::vector<char>& buf);
};
struct InfoIteration {
@@ -231,7 +243,7 @@ class SearchManager: public ISearchManager {
};
SearchManager(const UpdateContext& updateContext) :
SearchManager(UpdateContext& updateContext) :
updates(updateContext) {}
void check_time(Search::Worker& worker) override;
@@ -254,7 +266,7 @@ class SearchManager: public ISearchManager {
size_t id;
const UpdateContext& updates;
UpdateContext& updates;
};
class NullSearchManager: public ISearchManager {
@@ -294,6 +306,28 @@ class Worker {
ContinuationHistory continuationHistory[2][2];
CorrectionHistory<Continuation> continuationCorrectionHistory;
#ifdef USE_MPI
struct {
std::mutex mutex;
Distributed::TTCache<Distributed::TTCacheSize> buffer = {};
} ttCache;
#endif
std::atomic<uint64_t> TTsaves;
friend void Distributed::save(TranspositionTable&,
ThreadPool&,
Search::Worker*,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
Bound b,
Depth d,
Move m,
Value ev,
uint8_t generation8);
TTMoveHistory ttMoveHistory;
SharedHistories& sharedHistory;
+2
View File
@@ -38,6 +38,7 @@
#include <array>
#include "../bitboard.h"
#include "../cluster.h"
#include "../misc.h"
#include "../movegen.h"
#include "../position.h"
@@ -494,6 +495,7 @@ class TBTables {
}
void info() const {
if (Distributed::is_root())
sync_cout << "info string Found " << foundWDLFiles << " WDL and " << foundDTZFiles
<< " DTZ tablebase files (up to " << MaxCardinality << "-man)." << sync_endl;
}
+6 -1
View File
@@ -28,8 +28,10 @@
#include <utility>
#include "bitboard.h"
#include "cluster.h"
#include "history.h"
#include "memory.h"
#include "misc.h"
#include "movegen.h"
#include "search.h"
#include "syzygy/tbprobe.h"
@@ -140,6 +142,7 @@ Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker
uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }
uint64_t ThreadPool::TT_saves() const { return accumulate(&Search::Worker::TTsaves); }
static size_t next_power_of_two(uint64_t count) { return count > 1 ? (2ULL << msb(count - 1)) : 1; }
@@ -148,7 +151,7 @@ static size_t next_power_of_two(uint64_t count) { return count > 1 ? (2ULL << ms
// Upon resizing, threads are recreated to allow for binding if necessary.
void ThreadPool::set(const NumaConfig& numaConfig,
Search::SharedState sharedState,
const Search::SearchManager::UpdateContext& updateContext) {
Search::SearchManager::UpdateContext& updateContext) {
if (threads.size() > 0) // destroy any existing thread(s)
{
@@ -332,6 +335,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,
for (auto&& th : threads)
th->wait_for_search_finished();
Distributed::signals_init();
main_thread()->start_searching();
}
+4 -3
View File
@@ -29,6 +29,7 @@
#include <vector>
#include "memory.h"
#include "movepick.h"
#include "numa.h"
#include "position.h"
#include "search.h"
@@ -137,14 +138,14 @@ class ThreadPool {
void wait_on_thread(size_t threadId);
size_t num_threads() const;
void clear();
void set(const NumaConfig& numaConfig,
Search::SharedState,
const Search::SearchManager::UpdateContext&);
void
set(const NumaConfig& numaConfig, Search::SharedState, Search::SearchManager::UpdateContext&);
Search::SearchManager* main_manager();
Thread* main_thread() const { return threads.front().get(); }
uint64_t nodes_searched() const;
uint64_t tb_hits() const;
uint64_t TT_saves() const;
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
+1
View File
@@ -21,6 +21,7 @@
#include <cstdint>
#include "cluster.h"
#include "misc.h"
namespace Stockfish {
+13
View File
@@ -28,6 +28,10 @@
namespace Stockfish {
namespace Distributed {
void init();
}
class ThreadPool;
struct TTEntry;
struct Cluster;
@@ -52,7 +56,12 @@ struct TTData {
Bound bound;
bool is_pv;
#ifdef USE_MPI
// We need this for TTCache to be constructible.
TTData() = default;
#else
TTData() = delete;
#endif
// clang-format off
TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) :
@@ -73,6 +82,8 @@ struct TTWriter {
private:
friend class TranspositionTable;
friend void Distributed::init();
TTEntry* entry;
TTWriter(TTEntry* tte);
};
@@ -80,6 +91,8 @@ struct TTWriter {
class TranspositionTable {
friend void Distributed::init();
public:
~TranspositionTable() { aligned_large_pages_free(table); }
+17 -10
View File
@@ -30,6 +30,7 @@
#include <vector>
#include "benchmark.h"
#include "cluster.h"
#include "engine.h"
#include "memory.h"
#include "movegen.h"
@@ -94,7 +95,8 @@ void UCIEngine::loop() {
do
{
if (cli.argc == 1
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
&& !Distributed::getline(std::cin,
cmd)) // Wait for an input or an end-of-file (EOF) indication
cmd = "quit";
std::istringstream is(cmd);
@@ -112,7 +114,7 @@ void UCIEngine::loop() {
else if (token == "ponderhit")
engine.set_ponderhit(false);
else if (token == "uci")
else if (token == "uci" && Distributed::is_root())
{
sync_cout << "id name " << engine_info(true) << "\n"
<< engine.get_options() << sync_endl;
@@ -133,7 +135,7 @@ void UCIEngine::loop() {
position(is);
else if (token == "ucinewgame")
engine.search_clear();
else if (token == "isready")
else if (token == "isready" && Distributed::is_root())
sync_cout << "readyok" << sync_endl;
// Add custom non-UCI commands, mainly for debugging purposes.
@@ -144,13 +146,13 @@ void UCIEngine::loop() {
bench(is);
else if (token == BenchmarkCommand)
benchmark(is);
else if (token == "d")
else if (token == "d" && Distributed::is_root())
sync_cout << engine.visualize() << sync_endl;
else if (token == "eval")
else if (token == "eval" && Distributed::is_root())
engine.trace_eval();
else if (token == "compiler")
else if (token == "compiler" && Distributed::is_root())
sync_cout << compiler_info() << sync_endl;
else if (token == "export_net")
else if (token == "export_net" && Distributed::is_root())
{
std::pair<std::optional<std::string>, std::string> files[2];
@@ -162,7 +164,9 @@ void UCIEngine::loop() {
engine.save_network(files);
}
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
else if ((token == "--help" || token == "help" || token == "--license"
|| token == "license")
&& Distributed::is_root())
sync_cout
<< "\nStockfish is a powerful chess engine for playing and analyzing."
"\nIt is released as free software licensed under the GNU GPLv3 License."
@@ -171,7 +175,7 @@ void UCIEngine::loop() {
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
<< sync_endl;
else if (!token.empty() && token[0] != '#')
else if (!token.empty() && token[0] != '#' && Distributed::is_root())
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
<< sync_endl;
@@ -252,6 +256,7 @@ void UCIEngine::bench(std::istream& args) {
if (token == "go" || token == "eval")
{
if (Distributed::is_root())
std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << engine.fen() << ")"
<< std::endl;
if (token == "go")
@@ -269,7 +274,7 @@ void UCIEngine::bench(std::istream& args) {
nodes += nodesSearched;
nodesSearched = 0;
}
else
else if (Distributed::is_root())
engine.trace_eval();
}
else if (token == "setoption")
@@ -287,6 +292,7 @@ void UCIEngine::bench(std::istream& args) {
dbg_print();
if (Distributed::is_root())
std::cerr << "\n===========================" //
<< "\nTotal time (ms) : " << elapsed //
<< "\nNodes searched : " << nodes //
@@ -461,6 +467,7 @@ void UCIEngine::setoption(std::istringstream& is) {
std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) {
auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]);
if (Distributed::is_root())
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
return nodes;
}
+2 -1
View File
@@ -26,6 +26,7 @@
#include <sstream>
#include <utility>
#include "cluster.h"
#include "misc.h"
namespace Stockfish {
@@ -54,7 +55,7 @@ void OptionsMap::setoption(std::istringstream& is) {
if (options_map.count(name))
options_map[name] = value;
else
else if (Distributed::is_root())
sync_cout << "No such option: " << name << sync_endl;
}