goshape 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: goshape
3
+ Version: 0.1.0
4
+ Summary: Shape Habits Analysis and Personalized Evaluation
5
+ Author: Sander Land
6
+ License-File: LICENSE
7
+ Requires-Python: <3.14,>=3.10
8
+ Requires-Dist: httpx>=0.25.0
9
+ Requires-Dist: numpy>=2.1.2
10
+ Requires-Dist: pyqtgraph>=0.13.7
11
+ Requires-Dist: pysgf>=0.9.0
12
+ Requires-Dist: pyside6>=6.5.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # SHAPE: Shape Habits Analysis and Personalized Evaluation
16
+
17
+ SHAPE is an app to play Go with AI feedback, specifically designed to point out typical bad habits for your current skill level.
18
+
19
+ This is an experimental project, and is unlikely to ever become very polished.
20
+
21
+ ## Quick Start
22
+
23
+ Run the application directly using `uvx`:
24
+
25
+ ```bash
26
+ uvx goshape
27
+ ```
28
+
29
+ The first time you run this, `uv` will automatically download the package, create a virtual environment, and install all dependencies.
30
+
31
+ When the application starts for the first time, it will check for the required KataGo models in `~/.katrain/`. If they are not found, a dialog will appear to guide you through downloading them.
32
+
33
+ ## Manual
34
+
35
+ The most important settings are:
36
+
37
+ - Current Rank: Your current Go skill level, which determines which mistakes are considered "typical" for your level.
38
+ - Target Rank: The skill level you want to aim for. Even if a move is a huge mistake, if it was a mistake still common at that level, it won't be considered relevant.
39
+ - Opponent: The type and level of the AI opponent.
40
+ - This supports modern, pre-alphago style, and historical professional style.
41
+ - Like the feedback, it is likely to be somewhat weaker than actual professionals or high-dan players.
42
+
43
+ The game will automatically halt when a typical mistake is made by you, allowing you to analyze, undo, or just continue.
44
+
45
+ Keep in mind that the techniques used are more likely to be helpful up to low-dan levels, and may not be helpful at all at high levels.
46
+
47
+ ### Heatmap
48
+
49
+ The policy heatmap shows the probability of the top moves being made for your current rank, target rank, and AI.
50
+ Note that a move being probable does not mean it is a good move.
51
+ You can select multiple heatmaps to get a blended view, where size/number is the average probability, and the color is the average rank (current, target, AI).
52
+
53
+
54
+ ## TODO list from Gemini
55
+
56
+ Based on a code review, here are some suggested areas for improvement:
57
+
58
+ ### High Impact
59
+ - **User-Friendly Errors (`main.py`):** Show GUI dialogs for errors instead of crashing the application.
60
+
61
+ ### Medium Impact
62
+ - **Refactor `GameNode` (`game_logic.py`):** Extract board state and rule logic into a separate `Board` class to simplify `GameNode` and improve modularity.
63
+
64
+ ### Low Impact
65
+ - **Code Clarity (`game_logic.py`):** Improve code readability.
66
+
@@ -0,0 +1,17 @@
1
+ shape/game_logic.py,sha256=oJDMmOubsotVwybN-2zx2lOFVofxqGzDddQk9FV5TX0,10092
2
+ shape/main.py,sha256=evEBAUwIk479yVqsmE4w696-DHzicR_e9rF8bw_ruDs,1050
3
+ shape/utils.py,sha256=Of8J3dY12H_iyYnpxNPUUZoicNkcxW7GsZvlawgLp-M,190
4
+ shape/katago/analysis.cfg,sha256=1HrXtB74A4LK-prJOr4c5iwewv21CAlypE27A4lKnN0,11958
5
+ shape/katago/downloader.py,sha256=WfZsXx_ljYtcYgBb1vjC-_JnAl36GPmwhyafkjEEyD0,16430
6
+ shape/katago/engine.py,sha256=wQerIFLI4s0jd1SPQCmLs5f-2-uCwS8bNbHw90K-MGo,6913
7
+ shape/ui/board_view.py,sha256=VIcJQp_z3Gax4hA89T71PF3uzw1P04jgEZ8h0jm0TtE,12958
8
+ shape/ui/main_window.py,sha256=PRAY6kFnfaVNNkUynAQ9na1SdisjAluDx8ct2TRn078,13655
9
+ shape/ui/tab_analysis.py,sha256=oEG3SdyYkphqXzv-Z49yLPbUSzRiH47L7P2Codz5qng,4316
10
+ shape/ui/tab_config.py,sha256=jfGbQ6M_guMNF1jj7VMnJS2CBCOi7AoswnviASyNgss,2896
11
+ shape/ui/tab_main_control.py,sha256=Pv4S0IPwvOMIlaWp-Zolk5X8er8rKKQLAZBBfFB-R8Q,13170
12
+ shape/ui/ui_utils.py,sha256=kBV5F7lX-uV8RZRaVs3PCoundC77ZE_vW2Sc8fiUzI8,2872
13
+ goshape-0.1.0.dist-info/METADATA,sha256=x4QqTkOCRlJ7VQ5DaxgrlzPneQv9mET_y0vpfUJpgsI,2712
14
+ goshape-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ goshape-0.1.0.dist-info/entry_points.txt,sha256=Aal5yaSsJF01WZpewmQDCat4APe3DSz2hsXbtCSPjko,44
16
+ goshape-0.1.0.dist-info/licenses/LICENSE,sha256=7vQREz1XrvLQyCJl_0rF5nh51cx9Wv5UkPuuXjyAGQU,2556
17
+ goshape-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ goshape = shape.main:main
@@ -0,0 +1,40 @@
1
+ This repository includes:
2
+
3
+ 1. Binaries for 'KataGo', which is Copyright David J Wu et al.
4
+ For on related licenses for these binaries and libraries see https://github.com/lightvector/KataGo
5
+
6
+ 2. Icons from www.flaticon.com, used with permission with the following attributions:
7
+ - Equalize icon and Thrash Icon: derived from work by bqlqn from www.flaticon.com
8
+ - Other Menu icons, Finish, Collaboration and Flag icons: derived from work by Freepik from www.flaticon.com
9
+ - Collapse branch icon: derived from work by Kirill Kazachek from www.flaticon.com
10
+ - Prune icon: derived from work by Pixelmeetup from www.flaticon.com
11
+ - Reset icon: derived from work by Pixel Perfect from www.flaticon.com
12
+ - Rotate icon: derived from work by Frey Wazza from www.flaticon.com
13
+
14
+ 3. The True Type Font DIGITAL-7 version 1.02 by Sizenko Alexander, which is free for non-commercial use.
15
+
16
+ 4. The Noto Sans fonts from google which are covered by the SIL open font license v1.1 included in the katrain/fonts directory.
17
+
18
+ -----------------------------------------------------------------------------------------
19
+ Aside from the above, the license for all other content in this repository is as follows:
20
+ -----------------------------------------------------------------------------------------
21
+
22
+ Copyright 2020 Sander Land and/or other authors of the content in this repository.
23
+ (See 'CONTRIBUTIONS.md' file for a list of authors as well as other indirect contributors).
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
26
+ associated documentation files (the "Software"), to deal in the Software without restriction,
27
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
28
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
29
+ furnished to do so, subject to the following conditions:
30
+
31
+ The above copyright notice and this permission notice shall be included in all copies or
32
+ substantial portions of the Software.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
35
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
37
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
+
40
+ -----------------------------------------------------------------------------------------
shape/game_logic.py ADDED
@@ -0,0 +1,265 @@
1
+ import copy
2
+
3
+ import numpy as np
4
+ from pysgf import SGF, Move, SGFNode
5
+
6
+ from shape.utils import setup_logging
7
+
8
+ logger = setup_logging()
9
+
10
+
11
+ class PolicyData:
12
+ @staticmethod
13
+ def grid_from_data(policy_data: list[float] | np.ndarray):
14
+ size = int(len(policy_data) ** 0.5)
15
+ return np.reshape(policy_data[:-1], (size, size))[::-1]
16
+
17
+ def __init__(self, policy_data: list[float] | np.ndarray):
18
+ self.data = np.array(policy_data)
19
+ self.grid = self.grid_from_data(self.data)
20
+ self.pass_prob = self.data[-1]
21
+ self.max_prob = np.max(self.data)
22
+
23
+ def at(self, move: Move) -> tuple[float, float]:
24
+ if move.is_pass:
25
+ return self.pass_prob, self.pass_prob / self.max_prob
26
+ col, row = move.coords
27
+ return self.grid[row][col], self.grid[row][col] / self.max_prob
28
+
29
+ def sample(
30
+ self,
31
+ top_k: int = 10000,
32
+ top_p: float = 1e9,
33
+ min_p: float = 0.0,
34
+ exclude_pass: bool = True,
35
+ secondary_data: np.ndarray | None = None,
36
+ ) -> tuple[list[tuple[Move, float, float]], str]:
37
+ secondary_data_data = secondary_data if secondary_data is not None else self.grid
38
+ moves = [
39
+ (Move(coords=(col, row)), prob, d)
40
+ for row, (policy_row, secondary_data_row) in enumerate(zip(self.grid, secondary_data_data, strict=False))
41
+ for col, (prob, d) in enumerate(zip(policy_row, secondary_data_row, strict=False))
42
+ if prob > 0
43
+ ]
44
+ if self.pass_prob > 0 and not exclude_pass:
45
+ moves.append(("pass", self.pass_prob, None))
46
+ moves.sort(key=lambda x: x[1], reverse=True)
47
+ highest_prob = moves[0][1]
48
+ top_moves = []
49
+ total_prob = 0
50
+ for i, (move, prob, *data) in enumerate(moves, 1):
51
+ if prob < min_p * highest_prob:
52
+ return top_moves, "min_p"
53
+ top_moves.append((move, prob, *data))
54
+ total_prob += prob
55
+
56
+ if i == top_k:
57
+ return top_moves, "top_k"
58
+ if total_prob >= top_p:
59
+ return top_moves, "top_p"
60
+
61
+ return top_moves, "all"
62
+
63
+
64
+ class Analysis:
65
+ REQUESTED = object()
66
+
67
+ def __init__(self, key: str | None, data: dict):
68
+ self.key = key
69
+ self.data = data
70
+ self.ai_policy = PolicyData(data.pop("policy"))
71
+ if "humanPolicy" in data:
72
+ self.human_policy = PolicyData(data.pop("humanPolicy"))
73
+ else:
74
+ assert key is None, f"Expected human policy for key {key}"
75
+ self.human_policy = self.ai_policy
76
+
77
+ @property
78
+ def root_info(self) -> dict:
79
+ return self.data.get("rootInfo", {})
80
+
81
+ def ai_score(self) -> float | None:
82
+ return self.root_info.get("scoreLead")
83
+
84
+ def win_rate(self) -> float | None:
85
+ return self.root_info.get("winrate")
86
+
87
+ def visit_count(self) -> int:
88
+ return self.root_info.get("visits", 0)
89
+
90
+ def ai_moves(self) -> list:
91
+ return self.data.get("moveInfos", [])
92
+
93
+
94
+ class GameNode(SGFNode):
95
+ def __init__(self, parent: "GameNode | None" = None, properties=None, move=None):
96
+ super().__init__(parent, properties, move)
97
+ if parent:
98
+ assert move is not None
99
+ self.board_state = self._board_state_after_move(parent.board_state, move)
100
+ else:
101
+ bx, by = self.board_size
102
+ self.board_state: list[list[str | None]] = [[None for _ in range(bx)] for _ in range(by)]
103
+ self.analyses = {}
104
+ self.ai_move_requested = False # flag to indicate if ai move was manually requested
105
+ self.autoplay_halted_reason: str | None = None # flag to indicate if autoplay was automatically halted
106
+
107
+ @property
108
+ def square_board_size(self) -> int:
109
+ bx, by = self.board_size
110
+ assert bx == by, "Non-square board size not supported"
111
+ return bx
112
+
113
+ def game_ended(self) -> bool:
114
+ return self.is_pass and self.parent.is_pass
115
+
116
+ def delete_child(self, child: "GameNode"):
117
+ self.children = [c for c in self.children if c is not child]
118
+
119
+ def _board_state_after_move(self, board_state: list[list[str | None]], move: Move) -> list[list[str | None]]:
120
+ new_board_state = copy.deepcopy(board_state)
121
+ if move.is_pass:
122
+ return new_board_state
123
+ col, row = move.coords
124
+ new_board_state[row][col] = move.player
125
+ captured = self._remove_captured_stones(new_board_state, col, row, move.opponent)
126
+ if captured:
127
+ self._remove_group(new_board_state, captured)
128
+ else:
129
+ group = self._get_group(new_board_state, col, row)
130
+ if not self._group_has_liberties(new_board_state, group):
131
+ self._remove_group(new_board_state, group) # allow suicide
132
+ return new_board_state
133
+
134
+ def _is_valid_move(self, move: Move):
135
+ if move.is_pass:
136
+ return True
137
+ col, row = move.coords
138
+ if not (0 <= row < len(self.board_state) and 0 <= col < len(self.board_state[0])):
139
+ logger.error("Point is out of bounds")
140
+ return False
141
+ if self.board_state[row][col] is not None:
142
+ logger.error("Point is already occupied")
143
+ return False
144
+ return True
145
+
146
+ def _remove_captured_stones(self, board_state, col, row, opponent):
147
+ captured = []
148
+ for dcol, drow in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
149
+ ncol, nrow = col + dcol, row + drow
150
+ if 0 <= nrow < len(board_state) and 0 <= ncol < len(board_state[0]) and board_state[nrow][ncol] == opponent:
151
+ group = self._get_group(board_state, ncol, nrow)
152
+ if not self._group_has_liberties(board_state, group):
153
+ self._remove_group(board_state, group)
154
+ captured.extend(group)
155
+ return captured
156
+
157
+ def _get_group(self, board_state, col, row):
158
+ color = board_state[row][col]
159
+ group = set()
160
+ stack = [(col, row)]
161
+ while stack:
162
+ ccol, crow = stack.pop()
163
+ if (ccol, crow) not in group:
164
+ group.add((ccol, crow))
165
+ for dcol, drow in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
166
+ ncol, nrow = ccol + dcol, crow + drow
167
+ if (
168
+ 0 <= nrow < len(board_state)
169
+ and 0 <= ncol < len(board_state[0])
170
+ and board_state[nrow][ncol] == color
171
+ ):
172
+ stack.append((ncol, nrow))
173
+ return group
174
+
175
+ def _has_liberties(self, board_state, col, row):
176
+ for dcol, drow in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
177
+ ncol, nrow = col + dcol, row + drow
178
+ if 0 <= nrow < len(board_state) and 0 <= ncol < len(board_state[0]) and board_state[nrow][ncol] is None:
179
+ return True
180
+ return False
181
+
182
+ def _remove_group(self, board_state, group):
183
+ for col, row in group:
184
+ board_state[row][col] = None
185
+
186
+ def _group_has_liberties(self, board_state, group):
187
+ return any(self._has_liberties(board_state, col, row) for col, row in group)
188
+
189
+ def store_analysis(self, analysis: dict, key: str | None):
190
+ current_analysis = self.get_analysis(key)
191
+ parsed_analysis = Analysis(key, analysis)
192
+ if current_analysis and current_analysis.visit_count() >= parsed_analysis.visit_count():
193
+ return # ignore if we already have a better analysis
194
+ self.analyses[key] = parsed_analysis
195
+
196
+ def mark_analysis_requested(self, key: str | None):
197
+ if key not in self.analyses:
198
+ self.analyses[key] = Analysis.REQUESTED # requested but not yet received
199
+
200
+ def analysis_requested(self, key: str | None):
201
+ return key in self.analyses # None means requested but not yet received
202
+
203
+ def get_analysis(self, key: str | None, parent: bool = False) -> Analysis | None:
204
+ if parent:
205
+ return self.parent.get_analysis(key) if self.parent else None
206
+ analysis = self.analyses.get(key)
207
+ return None if analysis is Analysis.REQUESTED else analysis
208
+
209
+ def mistake_size(self) -> float | None:
210
+ current_analysis = self.get_analysis(None)
211
+ parent_analysis = None
212
+ if self.parent:
213
+ parent_analysis = self.parent.get_analysis(None)
214
+
215
+ if not current_analysis or not parent_analysis:
216
+ return None
217
+
218
+ current_score = current_analysis.ai_score()
219
+ parent_score = parent_analysis.ai_score()
220
+ if current_score is None or parent_score is None:
221
+ return None
222
+
223
+ score_diff = current_score - parent_score
224
+ return score_diff if self.player == "W" else -score_diff
225
+
226
+
227
+ class ShapeSGF(SGF):
228
+ _NODE_CLASS = GameNode
229
+
230
+
231
+ class GameLogic:
232
+ def __init__(self):
233
+ self.new_game()
234
+
235
+ def new_game(self, board_size=19, **rules):
236
+ self.current_node = GameNode(properties={"RU": "JP", "KM": 6.5, "SZ": board_size, **rules})
237
+
238
+ def __getattr__(self, attr):
239
+ if not hasattr(self.current_node, attr):
240
+ raise AttributeError(f"'GameLogic' object has no attribute '{attr}'")
241
+ return getattr(self.current_node, attr)
242
+
243
+ def make_move(self, move: Move) -> bool:
244
+ if not self.current_node._is_valid_move(move):
245
+ return False
246
+ self.current_node = self.current_node.play(move)
247
+ return True
248
+
249
+ def undo_move(self, n: int = 1):
250
+ while (n := n - 1) >= 0 and self.current_node.parent:
251
+ self.current_node = self.current_node.parent
252
+
253
+ def redo_move(self, n: int = 1):
254
+ while (n := n - 1) >= 0 and self.current_node.children:
255
+ self.current_node = self.current_node.children[0]
256
+
257
+ def get_score_history(self) -> list[tuple[int, float]]:
258
+ nodes = self.current_node.nodes_from_root
259
+ return [(node.depth, ai_analysis.ai_score()) for node in nodes if (ai_analysis := node.get_analysis(None))]
260
+
261
+ def export_sgf(self, player_names):
262
+ return self.current_node.root.sgf()
263
+
264
+ def import_sgf(self, sgf_data: str):
265
+ self.current_node = ShapeSGF.parse(sgf_data)
@@ -0,0 +1,215 @@
1
+ # Example config for C++ (non-python) gtp bot
2
+
3
+ # SEE NOTES ABOUT PERFORMANCE AND MEMORY USAGE IN gtp_example.cfg
4
+ # SEE NOTES ABOUT numSearchThreads AND OTHER IMPORTANT PARAMS BELOW!
5
+
6
+ # Logs------------------------------------------------------------------------------------
7
+
8
+ # Where to output log?
9
+ # logFile = analysis.log # Use this instead of logDir to just specify a single file directly
10
+ # logToStderr = true # Echo everything output to log file to stderr as well
11
+ # logAllRequests = false # Log all input lines received to the analysis engine.
12
+ # logAllResponses = false # Log all lines output to stdout from the analysis engine.
13
+ # logSearchInfo = false # Log debug info for every search performed
14
+
15
+ # Controls the number of moves after the first move in a variation.
16
+ # analysisPVLen = 15
17
+
18
+ # Report winrates for analysis as (BLACK|WHITE|SIDETOMOVE).
19
+ reportAnalysisWinratesAs = BLACK
20
+
21
+ # Bot behavior---------------------------------------------------------------------------------------
22
+
23
+ # Handicap -------------
24
+
25
+ # Assume that if black makes many moves in a row right at the start of the game, then the game is a handicap game.
26
+ # This is necessary on some servers and for some GUIs and also when initializing from many SGF files, which may
27
+ # set up a handicap games using repeated GTP "play" commands for black rather than GTP "place_free_handicap" commands.
28
+ # However, it may also lead to incorrect undersanding of komi if whiteBonusPerHandicapStone = 1 and a server does NOT
29
+ # have such a practice.
30
+ # Defaults to true! Uncomment and set to false to disable this behavior.
31
+ # assumeMultipleStartingBlackMovesAreHandicap = true
32
+
33
+ # Passing and cleanup -------------
34
+
35
+ # Make the bot never assume that its pass will end the game, even if passing would end and "win" under Tromp-Taylor rules.
36
+ # Usually this is a good idea when using it for analysis or playing on servers where scoring may be implemented non-tromp-taylorly.
37
+ # Defaults to true! Uncomment and set to false to disable this.
38
+ conservativePass = true
39
+
40
+ # When using territory scoring, self-play games continue beyond two passes with special cleanup
41
+ # rules that may be confusing for human players. This option prevents the special cleanup phases from being
42
+ # reachable when using the bot for GTP play.
43
+ # Defaults to true! Uncomment and set to false if you want KataGo to be able to enter special cleanup.
44
+ # For example, if you are testing it against itself, or against another bot that has precisely implemented the rules
45
+ # documented at https://lightvector.github.io/KataGo/rules.html
46
+ # preventCleanupPhase = true
47
+
48
+ # Search limits-----------------------------------------------------------------------------------
49
+
50
+ # By default, if NOT specified in an individual request, limit maximum number of root visits per search to this much
51
+ maxVisits = 500
52
+ # If provided, cap search time at this many seconds
53
+ # maxTime = 60
54
+
55
+
56
+ # numSearchThreads is the number of threads to use in each MCTS tree search in parallel for any individual position.
57
+ # But NOTE: Analysis engine also specifies max number of POSITIONS to be able to search in parallel via command line
58
+ # argument, -num-analysis-threads.
59
+
60
+ # Parallelization across positions is more efficient since the threads on different positions operate
61
+ # on different MCTS trees so they don't have to synchronize with each other. Also, multiple threads on the same MCTS
62
+ # tree weakens the search (holding playouts fixed) due to out of date statistics on nodes and suboptimal exploration,
63
+ # although the loss is still quite small for only 2,4,8 threads. So you often want to keep numSearchThreads small,
64
+ # unlike in GTP.
65
+
66
+ # But obviously you only get the benefit of parallelization across positions when you actually have lots of positions
67
+ # that you are querying at once.
68
+
69
+ # Therefore:
70
+ # * If you plan to use the analysis engine only for batch processing large numbers of positions,
71
+ # it's preferable to set this to only a small number (e.g. 1,2,4) and use a higher -num-analysis-threads.
72
+ # * But if you sometimes plan to query the analysis engine for single positions, or otherwise in smaller quantities
73
+ # than -num-analysis-threads, or if you plan to be user-interactive such that the response time on some individual
74
+ # analysis requests is important to keep low, then set this to a larger number and use somewhat fewer analysis threads,
75
+ # That way, individual searches complete faster due to having more threads on each one and doing fewer other ones at a time.
76
+
77
+ # For 19x19 boards, weaker GPUs probably want a TOTAL number of threads (numSearchThreads * num-analysis-threads)
78
+ # between 4 and 32. Mid-tier GPUs probably between 16 and 64. Strong GPUs probably between 32 and 256.
79
+ # But there's no substitute for experimenting and seeing what's best for your hardware and your usage case.
80
+ # Keep in mind that the number of threads you want doesn't necessarily have much to do with how many cores you
81
+ # have on your system, and could easily exceed the number of cores. GPU batching is (usually) the dominant consideration.
82
+ numAnalysisThreads = 12
83
+ numSearchThreads = 4
84
+
85
+ # nnMaxBatchSize is the max number of positions to send to a single GPU at once. Generally, it should be the case that:
86
+ # (number of GPUs you will use * nnMaxBatchSize) >= (numSearchThreads * num-analysis-threads)
87
+ # That way, when each threads tries to request a GPU eval, your batch size summed across GPUs is large enough to handle them
88
+ # all at once. However, it can be sensible to set this a little smaller if you are limited on GPU memory,
89
+ # too large a number may fail if the GPU doesn't have enough memory.
90
+ nnMaxBatchSize = 96
91
+
92
+ # Eigen-specific settings--------------------------------------
93
+ # These only apply when using the Eigen (pure CPU) version of KataGo.
94
+
95
+ # This is the number of CPU threads for evaluating the neural net on the Eigen backend.
96
+ # It defaults to min(numAnalysisThreads * numSearchThreadsPerAnalysisThread, numCPUCores).
97
+ # numEigenThreadsPerModel = X
98
+
99
+ # Uncomment and set these smaller if you ONLY are going to use the analysis engine for smaller boards (or plan to
100
+ # run multiple instances, with some instances only handling smaller boards). It should improve performance.
101
+ # It may also mean you can use more threads profitably.
102
+ # maxBoardXSizeForNNBuffer = 19
103
+ # maxBoardYSizeForNNBuffer = 19
104
+
105
+ # TO USE MULTIPLE GPUS:
106
+ # Uncomment and set this to the number of GPUs you have and/or would like to use...
107
+ # AND if it is more than 1, uncomment the appropriate CUDA or OpenCL section below.
108
+ # numNNServerThreadsPerModel = 1
109
+
110
+ # Other General GPU Settings-------------------------------------------------------------------------------
111
+
112
+
113
+ # Cache up to 2 ** this many neural net evaluations in case of transpositions in the tree.
114
+ nnCacheSizePowerOfTwo = 20
115
+ # Size of mutex pool for nnCache is 2 ** this
116
+ nnMutexPoolSizePowerOfTwo = 16
117
+ # Randomize board orientation when running neural net evals?
118
+ nnRandomize = true
119
+
120
+ # TO USE MULTIPLE GPUS:
121
+ # Set this to the number of GPUs you have and/or would like to use...
122
+ # AND if it is more than 1, uncomment the appropriate CUDA or OpenCL section below.
123
+ # numNNServerThreadsPerModel = 1
124
+
125
+
126
+ # CUDA GPU settings--------------------------------------
127
+ # These only apply when using the CUDA version of KataGo.
128
+
129
+ # IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0
130
+ # cudaDeviceToUse = 0
131
+
132
+ # IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above):
133
+ # cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0
134
+ # cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1
135
+
136
+ # IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above):
137
+ # cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0
138
+ # cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1
139
+ # cudaDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2
140
+
141
+ # You can probably guess the pattern if you have four, five, etc. GPUs.
142
+
143
+ # KataGo will automatically use FP16 or not based on the compute capability of your NVIDIA GPU. If you
144
+ # want to try to force a particular behavior though you can uncomment these lines and change them
145
+ # to "true" or "false". E.g. it's using FP16 but on your card that's giving an error, or it's not using
146
+ # FP16 but you think it should.
147
+ # cudaUseFP16 = auto
148
+ # cudaUseNHWC = auto
149
+
150
+ # OpenCL GPU settings--------------------------------------
151
+ # These only apply when using the OpenCL version of KataGo.
152
+
153
+ # Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size
154
+ # openclReTunePerBoardSize = true
155
+
156
+ # IF USING ONE GPU: optionally uncomment and change this if the best device to use is guessed incorrectly.
157
+ # The default behavior tries to guess the 'best' GPU or device on your system to use, usually it will be a good guess.
158
+ # openclDeviceToUse = 0
159
+
160
+ # IF USING TWO GPUS: Uncomment these two lines and replace X and Y with the device ids of the devices you want to use.
161
+ # It might NOT be 0 and 1, some computers will have many OpenCL devices. You can see what the devices are when
162
+ # KataGo starts up - it should print or log all the devices it finds.
163
+ # (AND also set numNNServerThreadsPerModel above)
164
+ # openclDeviceToUseThread0 = X
165
+ # openclDeviceToUseThread1 = Y
166
+
167
+ # IF USING THREE GPUS: Uncomment these three lines and replace X and Y and Z with the device ids of the devices you want to use.
168
+ # It might NOT be 0 and 1 and 2, some computers will have many OpenCL devices. You can see what the devices are when
169
+ # KataGo starts up - it should print or log all the devices it finds.
170
+ # (AND also set numNNServerThreadsPerModel above)
171
+ # openclDeviceToUseThread0 = X
172
+ # openclDeviceToUseThread1 = Y
173
+ # openclDeviceToUseThread2 = Z
174
+
175
+ # You can probably guess the pattern if you have four, five, etc. GPUs.
176
+
177
+
178
+ # Root move selection and biases------------------------------------------------------------------------------
179
+ # Uncomment and edit any of the below values to change them from their default.
180
+ # Not all of these parameters are applicable to analysis, some are only used for actual play
181
+
182
+ # Temperature for the early game, randomize between chosen moves with this temperature
183
+ # chosenMoveTemperatureEarly = 0.5
184
+ # Decay temperature for the early game by 0.5 every this many moves, scaled with board size.
185
+ # chosenMoveTemperatureHalflife = 19
186
+ # At the end of search after the early game, randomize between chosen moves with this temperature
187
+ # chosenMoveTemperature = 0.10
188
+ # Subtract this many visits from each move prior to applying chosenMoveTemperature
189
+ # (unless all moves have too few visits) to downweight unlikely moves
190
+ # chosenMoveSubtract = 0
191
+ # The same as chosenMoveSubtract but only prunes moves that fall below the threshold, does not affect moves above
192
+ # chosenMovePrune = 1
193
+
194
+ # Number of symmetries to sample (WITH replacement) and average at the root
195
+ # rootNumSymmetriesToSample = 1
196
+
197
+ # Using LCB for move selection?
198
+ # useLcbForSelection = true
199
+ # How many stdevs a move needs to be better than another for LCB selection
200
+ # lcbStdevs = 5.0
201
+ # Policy temperature to use for move selection
202
+ # policyTemperature = 1.0
203
+
204
+ # ROOT POSITIONAL BIASES--------------
205
+ # Uncomment to have KataGo apply small biases to the root search results
206
+ # This can help reduce the "flat" evaluation of equal positions, but the bias applied is small.
207
+ # Per-channel parameters that control the impact on the root node evaluation of various aspects:
208
+ # Board edge proximity, areas next to already-played stones, corner proximity, etc.
209
+ # positionalityTuneX1 = 0.2
210
+ # positionalityTuneX2 = 0.0
211
+ # positionalityTuneY1 = 1.0
212
+ # positionalityTuneY2 = 0.15
213
+ # positionalityTuneCenterDistance = 0.4
214
+ # positionalityTuneOwnStoneProximity = 0.25
215
+ # positionalityTuneOppStoneProximity = 0.0