-
-
Notifications
You must be signed in to change notification settings - Fork 140
やねうら王の更新履歴2024
TODO :
残り作業
・large pageのテスト
・numa.h追加。
・ふかうら王のvast.aiの勉強会動画をYouTubeのやねちゃんねるにアップする。
- ふかうら王、mateの手数が最大になるように逃げるように…。
⇨ これ、あまり意味ないか…。
- 定跡のテスト
- テストコマンド
bench 1024 1 2 default depth
bench 1024 1 10 default depth
bench 1024 1 20 default depth
unittest auto_player_loop 10000 auto_player_depth 12
V8.36dev-f
- improvingの処理改善
- opponentWorsening導入。
V8.36dev-e
- mateの情報をTTに保存するの、もしかするとexcludedMoveありのとき保存したらあかんからか? ⇨ あー、excludedMoveがあるときはmate1ply呼び出してはなかった。 ⇨ またmate1ply()呼び出して詰みを発見したときはTTにwriteしてた。 ⇨ qsearch()では書き出してなかった。これ有効にしてみるか…。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_c.exe , eval = suisho10beta
T2,b1000,299 - 27 - 224(57.17% R50.17[24.94,75.4]) winrate black , white = 51.63% , 48.37%
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_d.exe , eval = suisho10beta
T2,b1000,289 - 33 - 188(60.59% R74.7[47.95,101.44]) winrate black , white = 52.83% , 47.17%
⇨ baseとこれとでは差があるからdev_c,dev_dの比較にならない。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_c.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_d.exe , eval = suisho10beta
T2,b1000,372 - 46 - 382(49.34% R-4.61[-25.41,16.19]) winrate black , white = 51.86% , 48.14%
⇨ わからん。有意差なさそう。
T2,b2000,184 - 17 - 179(50.69% R4.79[-25.17,34.75]) winrate black , white = 52.34% , 47.66%
V8.36dev-d
-
V8.36dev-cの修正、良かったので、これをqsearchのほうのevaluate()にも反映させる。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_b.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_c.exe , eval = suisho10beta T2,b1000,150 - 14 - 246(37.88% R-85.94[-115.5,-56.37]) winrate black , white = 53.54% , 46.46% T2,b2000,56 - 3 - 101(35.67% R-102.45[-149.91,-54.99]) winrate black , white = 52.87% , 47.13%
V8.36dev-c ⇨ V8.34baseとソースコードを比較しながら考える。
- search()のevaluateの処理間違ってた気がする。
// この局面で評価関数を呼び出したのか。(do_move()までには呼ばないと駄目)
// ※ やねうら王開発版にて独自追加
bool evaluated = false;
auto evaluate = [&](Position& pos) { evaluated = true; return (unadjustedStaticEval = ::evaluate(pos)); };
auto evaluate_test = [&](Position& pos) {
if (!evaluated)
{
evaluated = true;
unadjustedStaticEval = ::evaluate(pos);
}
};
こう書くのが正しいのでは?
evaluatedのフラグを端折って、unadjustedStaticEvalで代用しようと思うと、
unadjustedStaticEvalをstaticEvalとか代入してておかしいことになりそう。
-
(ss + 1) = excludedMoveの初期化削除 (ss + 1)->excludedMove = bestMove = Move::none(); ⇓ bestMove = Move::none();
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_b.exe , eval = suisho10beta T2,b1000,656 - 42 - 302(68.48% R134.76[114.9,154.62]) winrate black , white = 52.19% , 47.81% ⇨ baseからめさめさ弱くなっている。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_a.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_b.exe , eval = suisho10beta T2,b1000,583 - 54 - 623(48.34% R-11.53[-27.99,4.93]) winrate black , white = 50.41% , 49.59% T2,b2000,300 - 41 - 369(44.84% R-35.96[-58.16,-13.76]) winrate black , white = 50.52% , 49.48% T2,b4000,147 - 24 - 119(55.26% R36.71[1.53,71.88]) winrate black , white = 50.38% , 49.62% ⇨ これ長い時間でどうなのかよくわからない。他の修正を行ってから再度計測を行う。
V8.36dev-b
-
excludedMoveのとき、ss->staticEvalにも代入してみる。 unadjustedStaticEval = eval = /* ss->staticEval =*/ evaluate(pos); // ⇨ null moveなので、ss->staticEvalは破壊しないほうが良いのでは? ⇓ unadjustedStaticEval = eval = ss->staticEval = evaluate(pos);
⇨ これ大事っぽ。
unadjustedStaticEval = eval = ss->staticEval = evaluate(pos);
// ⇨ null moveなので、ss->staticEvalは親nodeでのstaticEvalをも指すので、
// ここで破壊しないほうが良いのでは?
// ⇨ 本来はそうだと言えるのだけど、親nodeでss->staticEvalは、このnodeでreturnしたあと、
// 用いておらず、また、このnode以降で、ss->staticeEvalを参照するので
// ここで更新しておいたほうが得であるようだ。
やねうらお — 今日 01:53
■ 前回までのあらすじ
Stockfishの最新版でMovePickerからkiller moveやcounter moveがごっそり消えた。大改造だが、これをどうにかやねうら王に反映させたら、R30ぐらい弱くなってしまった!
Stockfishのその後の探索部の諸々の改造を棋力計測をしながら一つ一つやねうら王に取り込んで、なんとかこのR30を取り戻さなければならない!
このR30が取り戻せないなら、MovePicker自体をrollbackしなければならないことになるが、それだと今後のStockfishの改良をやねうら王に取り込むのが非常に難しくなってしまう。
そのため、血眼になって失われたR30を取り戻すことに尽力するやねさんであった…
TO BE CONTINUED..
V8.36dev-a
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_x.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836dev_a.exe , eval = suisho10beta
T2,b1000,1685 - 163 - 1152(59.39% R66.06[55.14,76.98]) winrate black , white = 52.41% , 47.59%
T2,b2000,917 - 104 - 609(60.09% R71.1[56.17,86.03]) winrate black , white = 54.26% , 45.74%
T2,b4000,424 - 54 - 302(58.4% R58.94[37.44,80.45]) winrate black , white = 51.65% , 48.35%
⇨ くそー。R20ぐらい弱くなっとる。
- 開発版 : evaluate_test()追加。
- ValueAndPV を ValuePVとrename。
- なんかダサかったので…。
// この局面で評価関数を呼び出したのか。(do_move()までには呼ばないと駄目)
// ※ やねうら王開発版にて独自追加
auto evaluate = [&](Position& pos) { return (unadjustedStaticEval = ::evaluate(pos)); };
auto evaluate_test = [&](Position& pos) {
if (unadjustedStaticEval == VALUE_NONE)
unadjustedStaticEval = ::evaluate(pos);
};
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V836git_a.exe , eval = suisho10beta
T2,b1000,1525 - 161 - 1314(53.72% R25.87[15.12,36.62]) winrate black , white = 51.32% , 48.68%
⇨ 前変更で弱くはなってなさそう。
V8.36git-a // V8.34z8の次のバージョンをV8.36git-aとする。
-
VALUE_MAX_EVALの値を上方修正
-
VALUE_SUPERIORの値を上方修正
-
VALUE_MIN_EVAL追加。
-
1手詰めのスコア、置換表に書き出すときにvalue_to_tt()していたの修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_x.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_z.exe , eval = suisho10beta T2,b1000,1420 - 173 - 1407(50.23% R1.6[-9.15,12.34]) winrate black , white = 54.23% , 45.77% T2,b2000,674 - 111 - 675(49.96% R-0.26[-15.81,15.3]) winrate black , white = 51.15% , 48.85%
⇨ 少なくとも弱くはなってなさそうなので良しとする。
V8.35dev-z
- VALUE_MIN_EVAL追加。
- 1手詰めのスコア、置換表に書き出すときにvalue_to_tt()していたの修正。
V8.35dev-y
- VALUE_SUPERIORの値を上方修正
- VALUE_MAX_EVALの値を上方修正
やねうらお — 今日 07:14
■ やねうら王の評価値の上限値の変更について
やねうら王の評価値の上限(VALUE_MAX_EVAL)は、27000となっている。これは、VALUE_SUPERIOR(優等局面のスコア)が28000で、詰みのスコアの下限値はVALUE_MATE_IN_MAX_PLY(31754)となっている。
このVALUE_MAX_EVALを上方修正する。
具体的には、VALUE_SUPERIORを詰みのスコアの仲間入りをさせる。VALUE_MATE_IN_MAX_PLY - 1 (31753)にする。
そして、VALUE_MAX_EVALは、VALUE_SUPERIOR - 1(31752)に変更する。
VALUE_SUPERIORから引き算したりしないように探索部でVALUE_MAX_EVAL + 1以上は詰みのスコアとして取り扱うようにする。(これは、きちんと書けば、うまく取り扱えるようである)
結果として、VALUE_MAX_EVALが 27000 ⇨ 31752になるので、従来、入玉関係で27000を超える評価値になってしまっていたものも、31752までであれば、きちんと取り扱えるようになる。
FV_SCALEを16から20とか24に変更すると強くなっていたのは、もしかするとFV_SCALEが16だと評価値が上限である27000以上になってしまうからではないかと思う。
上の改良をすることによって、FV_SCALEは、20よりは16のほうが強いということになる可能性はある。
あと、従来の設定だと入玉で評価値が27000を超えてしまい、うまく指せなかった局面がうまく指せるようになって、勝率が上がる可能性がある。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_w.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_x.exe , eval = suisho10beta
T2,b1000,1517 - 163 - 1320(53.47% R24.16[13.41,34.92]) winrate black , white = 52.56% , 47.44%
T2,b2000,1371 - 192 - 1337(50.63% R4.36[-6.62,15.34]) winrate black , white = 53.18% , 46.82%
T2,b4000,664 - 126 - 590(52.95% R20.53[4.37,36.69]) winrate black , white = 53.51% , 46.49%
⇨ よわなっとる…。よく考えなおす…。
V8.35dev-x
- searchのevaluate()、やりすぎか?いくつかrollback ⇨ ひとつ前のバージョンと比較しつつ、調べていく。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_w.exe , eval = suisho10beta
T2,b1000,1649 - 131 - 1220(57.48% R52.34[41.56,63.13]) winrate black , white = 50.68% , 49.32%
T2,b2000,1163 - 163 - 934(55.46% R38.09[25.54,50.65]) winrate black , white = 53.65% , 46.35%
T2,b4000,430 - 64 - 326(56.88% R48.1[27.13,69.07]) winrate black , white = 53.17% , 46.83%
V8.34z7 V8.35dev-w
-
searchのほうもunadjustedStaticEval導入。
-
searchでEXCLUDED_MOVE_FORCE_EVALUATEが有効なとき、evaluate()呼び出してなかった箇所があるので修正。
-
searchのttWriter.writeでstaticEvalを保存していたところ、unadjustedStaticEvalを保存するように修正。
-
開発版、EXCLUDED_MOVE_FORCE_EVALUATEのオプション追加。
-
開発版、遅延evaluate()実装。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834z6.exe , eval = suisho10beta T2,b1000,835 - 74 - 691(54.72% R32.88[18.19,47.57]) winrate black , white = 53.01% , 46.99% T2,b2000,390 - 42 - 338(53.57% R24.86[3.64,46.08]) winrate black , white = 54.67% , 45.33% T2,b4000,392 - 85 - 323(54.83% R33.63[12.17,55.09]) winrate black , white = 51.33% , 48.67%
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_v.exe , eval = suisho10beta T2,b1000,1448 - 136 - 1146(55.82% R40.63[29.34,51.93]) winrate black , white = 50.85% , 49.15% T2,b2000,677 - 79 - 554(55.0% R34.83[18.47,51.2]) winrate black , white = 51.75% , 48.25% T2,b4000,307 - 60 - 243(55.82% R40.61[16.1,65.13]) winrate black , white = 52.36% , 47.64% T2,b8000,150 - 22 - 118(55.97% R41.68[6.58,76.78]) winrate black , white = 48.51% , 51.49% ⇨ R40差ぐらい。落ちなくなった。
V8.34z6 V8.35dev-v
-
unadjustedStaticEval導入。
-
to_corrected_static_eval導入。
-
qsearchに千日手チェック復活。
if (!evaluated) { Eval::evaluate_with_no_return(pos); evaluated=true;}
このコード、なんとかしたほうがいいなー。うむむ…。
benchの指し手が違うなー。何かバグらせてるかも。 あとで調べる。 ⇨ よくわからん。別にバグらせてはなかったか。
やねうらお — 今日 14:39
しかし開発版を対局させてるとまだ落ちる
原因わかった。
qsearch()で現局面で王手がかかっているときにss->staticEvalの初期化がなされていない。ss->staticEvalの初期化コードを消してしまっていた。(Stockfishでその初期化コードがなくなったから、初期化しなくて良いのかと勘違いした。)
そんなわけで、ss->staticEvalの代入とかするところをStockfishの最新のコードに倣うようにした。
次に、MovePickerが変わった影響か、qsearch()でTTの指し手で進めて千日手になって再帰的にqsearchの探索がMAX_PLYまでいくことがある。千日手チェック復活させて様子見。※ あとで調整する。
いまのコード、qsearchで王手がかかっているときにstaticEvalが未初期化になるのではないか。そうすると、それをTTに書き出しておかしくなるのでは? ⇨ それっぽい。
unittest auto_player_loop 10000 auto_player_depth 8
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_u.exe , eval = suisho10beta
T2,b1000,1197 - 121 - 972(55.19% R36.17[23.84,48.51]) winrate black , white = 52.56% , 47.44%
T2,b2000,617 - 101 - 542(53.24% R22.51[5.7,39.33]) winrate black , white = 53.67% , 46.33%
T2,b4000,291 - 55 - 244(54.39% R30.6[5.82,55.38]) winrate black , white = 50.65% , 49.35%
T2,b8000,121 - 12 - 97(55.5% R38.41[-0.46,77.27]) winrate black , white = 53.21% , 46.79%
V8.34z5 V8.35dev-u
- VALUE_MAX_EVALの取り扱い間違っていたの修正。
- TTEntry::_saveにassert追加。
やねうらお — 今日 08:58
置換表に書き出す時に値のassert追加したら、1手詰を見つけたときにevalとしてbestValueを書き込んでいたことに気づいた。bestValueが詰みのスコアのときにこれが置換表に書き込まれて、次にこのTTEntryを読み込んで、TT evalの値を用いてbestValueを更新してTTに書き込むと、TT valueの値がVALUE_INFINITEを超えてしまうということのようだ。
よって置換表に書き込むevalの値は abs(eval) <= VALUE_MAX_EVAL|| VALUE_NONE を守るようにすることでこれは修正される…はず…。
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, BOUND_EXACT,
std::min(MAX_PLY - 1, depth + 6), move, /* ss->staticEval */ bestValue, TT.generation());
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, BOUND_EXACT,
std::min(MAX_PLY - 1, depth + 6), move, /* ss->staticEval */ bestValue, TT.generation());
ここでbestValue >= MAX_EVAL超えてる値が書き込まれるのか…。
if (thisThread->nodes == 42408 && pos.game_ply()==188)
{
std::cout << "stop" << std::endl;
}
やねうらお — 今日 07:54
あー、qsearch()、千日手チェックは端折ったほうが強くなるから端折ったのかー。いまのコードだと256手ルールである指し手で257手目で連続王手の千日手が成立する場合、その指し手を引き分けの指し手と錯覚してしまう..🥲 (あとで修正する)
V8.34z4 V8.35dev-t
qsearch()はvalue_from_tt()でMATEスコアはply足し算して、value_to_ttでmateスコア引き算しているので、置換表の値は変わらないはず。
置換表にVALUE_INFINITE以上の値を書き込んでしまうのはsearch()がおかしいはずなのだが、mate distance pruningまわりがおかしいのか?
evalで範囲超えている可能性もあるか?
TTがちゃんとクリアできてない説もあるか?
-
最大手数で引き分けの処理、簡略化。
// 最大手数の判定 /* ■ 備考
pos.game_ply() == Limits.max_game_ply なら、最大手数に達しているが、 この局面で詰んでいたり、連続王手の千日手であったりすると、引き分けにはならない。 なので、 pos.game_ply() == Limits.max_game_ply という条件で即座に引き分けのスコアを返すのは誤り。 pos.game_ply() > Limits.max_game_ply という条件ならば、最大手数 + 1の局面だから、ここに到達したということは、 最大手数の局面では引き分けや連続王手の千日手などにならなかったということなので、 現局面(最大手数+1の局面)で引き分けのスコアを返すと良い。 (現局面で詰んでいても問題ない)
*/
256手ルールで 256手目の局面で判定を行う場合は、「詰まされていない、かつ、連続王手の千日手が成立していない」ならば、引き分けとしてreturnして良い。 257手目の局面で判定を行う場合は、この局面に到達したということは、256手目の局面で合法手があったということだから、引き分けとしてreturnして良い。
ということになる。1.の方式でやったほうが、256手目の局面で指し手生成とか1手ずつ試していくのとかが丸ごと端折れるので探索効率は良いのだが、コードが複雑になる。
2.の方式でやっていいならば、257手目の局面なら単にreturnするだけで済む。探索効率は少し悪いが、コードは極めてシンプルになる。
それで、よく考えると、 256手ルールで257手目の局面で連続王手の千日手が成立するときは引き分けでいいんやよね…?
上記の場合、257手目の局面へ至る指し手が非合法手ということになる気がする。ということは、この場合、負けである。
だから、2.の方式で判定するときは、この連続王手の千日手判定を先にやってから、257手目の局面であるかの判定を行う必要がある。
V8.35dev-s
- quietCheckEvasions削除
// 王手回避の指し手のうちquiet(駒を捕獲しない)な指し手の数
int quietCheckEvasions = 0;
// movecount pruning for quiet check evasions
// quietな指し手による王手回避のためのmovecountによる枝刈り。
// 王手回避でquietな指し手は良いとは思えないから、捕獲する指し手を好むようにする。
// だから、もし、qsearchで2つのquietな王手回避に失敗したら、
// そこ以降(captureから生成しているのでそこ以降もquietな指し手)も良くないと
// 考えるのは理に適っている。
// We prune after the second quiet check evasion move, where being 'in check' is
// implicitly checked through the counter, and being a 'quiet move' apart from
// being a tt move is assumed after an increment because captures are pushed ahead.
if (quietCheckEvasions > 1)
break;
quietCheckEvasions += !capture && ss->inCheck;
- 最大手数の判定処理を修正。
やねうらお — 今日 21:41
■ 落ちる原因調査結果
TT.probe()でabs(v) >= VALUE_INFINITE みたいな範囲外の値が返ってくることがある。
1.の原因を調べていると、qsearch()でそのような値を書き込んでしまうことがあることがわかった。
なぜ、そんな値を書き込むかと言うと、value_to_tt(bestValue, ss->ply)で詰みのスコアをrootからの手数で補正したときに範囲外になってしまうようだ。
以前のやねうら王のコードではmate distance pruningみたいなのがあって、こんな値になるやつbeta cutしてた気がするのだが、そのコードはどこにいってしまったのか?
最新のStockfishでは、to_corrected_static_eval()というevaluateした評価値を補正する関数のなかでついでにabs(v) < VALUE_INFNITE になるようにclampしているようだ。
こちらも5.のto_corrected_static_eval()を移植して、評価値の補正はしないものの、valueのclamp自体はそこで行うようにしたほうが良いのではないか?
かんがえちゅう🤔
V8.34z4
- ASSERT_LV5でunittest unittest auto_player_loop 10000 auto_player_depth 12 ⇨ なかなか落ちひんなー。 ⇨ 将棋所で対局させるわー。 ⇨ ここで落ちてもデバッグでけへんのか…。
unittest auto_player_loop 10000 auto_player_depth 16 ⇨ あー、これLEARNモードだから落ちない可能性があるんか…。
VSでビルドして、これで落ちるか調べよっと.. YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_vs.exe ⇨ 落ちるのかー。クラッシュダンプ用意できればそれでいいのかもなー。 YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_vs2.exe ⇨ debug assert有効にして落ちるか調べよう。
[31]>Error : ASSERT(-VALUE_INFINITE < value && value < VALUE_INFINITE), C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\source\engine\yaneuraou-engine\yaneuraou-search.cpp(2941): search [31]:Error! process terminated. ⇨ ここでおちたっぽ。
2378
[26]>Error : ASSERT(-VALUE_INFINITE < nullValue && nullValue < VALUE_INFINITE), C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\source\engine\yaneuraou-engine\yaneuraou-search.cpp(2170): search
[15]>Error : ASSERT(-VALUE_INFINITE < value && value < VALUE_INFINITE), C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\source\engine\yaneuraou-engine\yaneuraou-search.cpp(1407): search
[2]>Error : ASSERT(-VALUE_INFINITE < value && value < VALUE_INFINITE), C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\source\engine\yaneuraou-engine\yaneuraou-search.cpp(3552): qsearch
if (!(-VALUE_INFINITE < value && value < VALUE_INFINITE))
{
std::cout << "Error! value = " << value << std::end;
std::this_thread::sleep_for(std::chrono::microseconds(10000));
exit(1);
}
[37]>Error : ASSERT(-VALUE_INFINITE < value_to_tt(bestValue, ss->ply) && value_to_tt(bestValue, ss->ply) < VALUE_INFINITE), C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\source\engine\yaneuraou-engine\yaneuraou-search.cpp(3993): qsearch [37]:Error! process terminated.
- ASSERT_LV5でbench evaldir ../build/nnue/eval bench // evaldir ../build/nnue/eval bench 64 4 28 default depth ⇨ おちひんっぽい。
適用検討中のcommit
-
MCP more after a bad singular search : https://github.com/official-stockfish/Stockfish/commit/b34a690cd4aa6d828ae0f47b427167f4e6392db7
-
Use futility margin in razoring margin : https://github.com/official-stockfish/Stockfish/commit/36eb9bc783d35842571d0d4313349b964892d9ca // ⇨ これあとでまとめて適用するから要らん。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834z3.exe , eval = suisho10beta T2,b1000,1528 - 158 - 1314(53.76% R26.21[15.46,36.96]) winrate black , white = 52.43% , 47.57% T2,b2000,733 - 92 - 665(52.43% R16.91[1.62,32.21]) winrate black , white = 52.79% , 47.21% ⇨ 長い時間でマシなのかも知れない。長い時間で計測したいが、落ちないようにするのが先決か。
V8.34z3
-
LowPlyHistoryの確保する配列サイズ間違っていたの修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_r.exe , eval = suisho10beta T2,b1000,1538 - 159 - 1283(54.52% R31.49[20.69,42.29]) winrate black , white = 51.29% , 48.71% T2,b2000,875 - 132 - 793(52.46% R17.09[3.09,31.1]) winrate black , white = 50.6% , 49.4%
V8.35dev-r
- LowPlyHistoryの確保する配列サイズ間違っていたの修正。
V8.35dev-q
-
multipleExtensionsのコード削除できてなかった。修正した。
-
ASSERT_LV5で8スレでbench 1024 8で完走している。これでなんで落ちるんか?
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834z2.exe , eval = suisho10beta T2,b1000,247 - 28 - 205(54.65% R32.38[5.41,59.35]) winrate black , white = 53.54% , 46.46% T2,b2000,101 - 9 - 110(47.87% R-14.83[-54.12,24.47]) winrate black , white = 51.18% , 48.82%
V8.34z2
- ttValueを参照していた箇所修正。
やねうらお — 今日 22:15
新しいMovePickerとLowPlyHistoryを適用したら、ときどき落ちる。(開発版、GitHub版ともに)
assertを有効にするとLowPlyHistoryでassertに引っかかる。
寝て起きてからStockfishのコードよく確認する。😴
やねうらお — 今日 22:17
GitHub版のコード、TTを新しいコードにしたときに、探索部で古い変数であるttValueを参照したままになっている箇所があった。この変数を削除して修正した。
いま棋力計測中。これで改造前と同じぐらいの差になってくれると嬉しいのだが…。🙄
https://github.com/yaneurao/YaneuraOu/commit/24517d9de1784befe488b2191eb29570f8fc99a8
V8.35dev-p
-
MovePicker porting
-
LowPlyHistory導入
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834z.exe , eval = suisho10beta T2,b1000,493 - 35 - 402(55.08% R35.45[16.26,54.64]) winrate black , white = 52.85% , 47.15% T2,b2000,210 - 30 - 170(55.26% R36.71[7.26,66.15]) winrate black , white = 53.42% , 46.58%
やねうらお — 今日 21:56
やねうら王のGitHubの最新のコードと3日前のコードと棋力を比較したところ、R35程度、最新のコードが弱かった。
・R200ぐらい弱かったのは修正できている。
・R35はMovePickerとqsearch()をいじったことによる影響と考えられる。
・開発版にまず上記のMovePickerの変更を適用して、それで棋力がどう変わるか計測中。
今回のMovePickerの変更でkiller moveやcounter moveが消滅してしまったのでそれを補うだけの探索部の調整をしないと以前と同じ強さにはならないのではないかと思うのだけど、だとして、MovePickerだけ適用して弱くなるのはまあ仕方がないというか、全体を最新のStockfishにキャッチアップしてから考えたほうがよさげ…。
V8.34z
- tt.probe()でMove::none()のときに置換表にhitしなかったことになっていたの修正。
- PARAM_DEFINE PARAM_FUTILITY_MARGIN_QUIET 280⇨170に戻す。
やねうらお — 今日 20:10
■ 新しいTTのコードについて
開発版のほうでStockfishの新しいTTのコードを適用して、変更前と等価なコードになっていることを確認した。(depth固定benchコマンドで同じ探索ノード数になった)
置換表からとってきた指し手(16bit move)をMove(32bit move)に変換して、それに失敗したら置換表にhitしなかったことにしていたのだが、そのコードが間違っていた。置換表の指し手がMove::none()である可能性を失念していた。つまりは、置換表の指し手がMove::none()であるとき、つねに置換表にhitしなかったことになっていた。
置換表になぜMove::none()が書き込まれているかということなのだが、Stockfishでは、evaluate()を呼び出した直後、これをのちの探索でその局面でのevaluate()を端折りたいがために、置換表にそのeval値を指し手 = Move::none()として記録するのだ。(このことを忘れていたのが今回のバグの原因)
ともかく、この修正された開発版のtt.cppをGitHubのコードに反映させた。現在棋力計測中。
GitHub版は、(手元の開発版のコードから)MovePickerとqsearch()を変更してしまっているので、まだ3日前のバージョンより弱い可能性がある。
ともかく、次は、新しいMovePickerを開発版に適用して弱くならないかを検証する。
https://github.com/yaneurao/YaneuraOu/commit/bc91b81fdaa2880c71218a50acd7cd941ad62af7
GitHub
- tt.probe()でMove::none()のときに置換表にhitしなかったことになっていたの修正。 · yaneurao/Ya...
- PARAM_DEFINE PARAM_FUTILITY_MARGIN_QUIET 280⇨170に戻す。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_o.exe , eval = suisho10beta
T2,b1000,508 - 67 - 525(49.18% R-5.72[-23.49,12.06]) winrate black , white = 51.31% , 48.69%
T2,b2000,243 - 36 - 241(50.21% R1.44[-24.52,27.39]) winrate black , white = 48.35% , 51.65%
V8.35dev-o V8.35dev-nとV835dev_baseと、両方プロジェクト作成して比較しながら実行してはどうか? ⇨ 一致した。TTでpos.to_move()呼び出すときに問題があった。ttMove == MOVE_NONEであるケースを忘れていた。
V8.35dev-n
-
TTまだおかしいのか…。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_m.exe , eval = suisho10beta T2,b1000,1269 - 131 - 690(64.78% R105.85[92.33,119.36]) winrate black , white = 50.48% , 49.52% T2,b2000,631 - 72 - 307(67.27% R125.16[105.28,145.03]) winrate black , white = 50.21% , 49.79%
V8.35dev-m
-
ttDepth, writeしてなかったの修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_l.exe , eval = suisho10beta T2,b1000,371 - 37 - 192(65.9% R114.43[89.05,139.81]) winrate black , white = 52.22% , 47.78% T2,b2000,177 - 17 - 96(64.84% R106.28[70.13,142.43]) winrate black , white = 52.75% , 47.25% ⇨ だいぶマシになったけど、まだR100ほど負けてるんだよなー。もうちょっとソースコードの比較をしよう。
V8.35dev-l
-
V8.35dev-kとV8.35dev-hとを比較してrollbackしていく。
-
ttData.moveに置換忘れていたところがあった。
-
PARAM_DEFINE PARAM_FUTILITY_MARGIN_QUIET = 170; 280⇨170に戻す。 // 変更してなかった。戻さなくて良かった。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_k.exe , eval = suisho10beta
T2,b1000,2182 - 130 - 688(76.03% R200.51[188.02,213.0]) winrate black , white = 50.56% , 49.44%
T2,b2000,2170 - 159 - 671(76.38% R203.89[191.28,216.51]) winrate black , white = 51.53% , 48.47%
V8.34y V8.35dev-k V8.35dev-j
- 新しいTTのUnitTest追加する。
- Mov16からの暗黙の変換を削除。
- Move16::none()など追加。
Move m = make_move(SQ_77,SQ_76,BLACK,PAWN);
u16 t;
t = m;
std::cout << t;
⇨ コンパイルエラー。(正しい) ⇨ TTはなぜエラーにならないのか? ⇨ Move16 = Move の形だからか…。この暗黙の変換、危ないのでは…。
Move m = make_move(SQ_77,SQ_76,BLACK,PAWN);
Move16 t;
t = m;
std::cout << t;
V8.34x
- LEARN版のメモリ確保のコードをrollbackする。 ⇨ TTのclearのコード、再度よく考える。
V8.35dev-i3
- LargeMemoryのコード掃除。
・TTのコードがバグっているのか、 TT以外のコードがバグっているのかがわからない。 どうしたもんか…。TTのコードだけ元に戻すか?ちょっと考える。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_i.exe , eval = suisho10beta
T2,b1000,1434 - 87 - 479(74.96% R190.49[175.41,205.56]) winrate black , white = 53.37% , 46.63%
T2,b2000,677 - 67 - 206(76.67% R206.69[183.97,229.41]) winrate black , white = 54.7% , 45.3%
V8.35dev-i
-
新TT導入。
-
misc.h、マージできるところはマージ。
-
ThreadIdOffset削除。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_h.exe , eval = suisho10beta T2,b1000,1426 - 163 - 1411(50.26% R1.84[-8.89,12.56]) winrate black , white = 51.18% , 48.82% T2,b2000,1403 - 212 - 1385(50.32% R2.24[-8.58,13.06]) winrate black , white = 52.12% , 47.88%
V8.35dev-h
- ValueList導入
V8.34w
- Moveをclass化したときに、LEARN版ででビルドが通っていなかったの修正。
V8.34v
- Moveをclass化したときに、ふかうら王でビルドが通っていなかったの修正その2。
V8.34u
-
Moveをclass化したときに、ふかうら王でビルドが通っていなかったの修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834t.exe , eval = suisho10beta T2,b1000,874 - 71 - 335(72.29% R166.59[148.23,184.94]) winrate black , white = 54.18% , 45.82%
V8.34t
-
Moveからのboolの変換はexplicitを指定とそれに伴う修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_g.exe , eval = suisho10beta T2,b1000,690 - 95 - 675(50.55% R3.82[-11.65,19.28]) winrate black , white = 53.63% , 46.37% T2,b2000,276 - 52 - 322(46.15% R-26.78[-50.2,-3.36]) winrate black , white = 49.83% , 50.17%
V8.35dev-g
-
std::map<Move, int64_t> votes;
を使うときに、mapのなかでMoveのoperator<()
を呼び出すのかー。これはどうしよう..- Move::Hash()を用いるといいようだ。修正。 ⇨ この修正をしないと、最後にbest threadを選出する処理でちょっと損をするか。
#if 0
// 合同法による擬似乱数生成器
// 探索で、excludedMoveを考慮した局面のhash keyが欲しいので、それを生成するために
// excludedMoveをseedとする擬似乱数を発生させる必要があり、そこで用いられる。
// cf. https://github.com/official-stockfish/Stockfish/commit/de24fcebc873ce2d65b30e039745dbc2e851f443
//
// やねうら王独自拡張
// pos.key()にxorをとるときにbit0(先後フラグ)を潰してはいけないので、
// ここではbit0が0になっている数(偶数)を返すことにした。
// あと、HashKeyとして128bitのkeyを使うとき(特殊用途)は、この関数は128bitの値を返さないといけない。(未対応)
constexpr Key make_key(uint64_t seed) {
return (seed * 6364136223846793005ULL + 1442695040888963407ULL) & ~1ULL;
}
// → この関数はStockfish 16で使わなくなった。
#endif
⇨ 削除
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834s.exe , eval = suisho10beta
T2,b1000,277 - 22 - 101(73.28% R175.26[142.1,208.43]) winrate black , white = 55.03% , 44.97%
⇨ だいぶ弱い。修正。
V8.34s
-
Moveをclass化したときの変更忘れ修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_f.exe , eval = suisho10beta T2,b1000,332 - 35 - 333(49.92% R-0.52[-22.67,21.62]) winrate black , white = 55.94% , 44.06% T2,b2000,162 - 28 - 140(53.64% R25.35[-7.57,58.28]) winrate black , white = 57.62% , 42.38%
V8.35dev-f
- Moveのoperator、macros.hで定義していた。削除。
bench 1024 1 10 default depth ⇨ 一致した。
// V834baseは元となる2024/10/1時点のGitHub版。V835dev_baseはdev版。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V835dev_base.exe , eval = suisho10beta
T2,b1000,1349 - 186 - 1465(47.94% R-14.33[-25.11,-3.55]) winrate black , white = 54.23% , 45.77%
T2,b2000,864 - 158 - 1058(44.95% R-35.19[-48.29,-22.09]) winrate black , white = 51.87% , 48.13%
V8.35dev-e
int from_to() const {
int a = (int)(from_sq() + (is_drop() ? (SQ_NB - 1) : 0)) * (int)SQ_NB + (int)to_sq();
int b = int(from_sq()) + int(is_drop() ? (SQ_NB - 1) : 0) * int(SQ_NB) + int(to_sq());
assert(a == b);
return int(from_sq()) + int(is_drop() ? (SQ_NB - 1) : 0) * int(SQ_NB) + int(to_sq());
}
⇨ このassert引っかかるのか…。なんぞこれ…。 ⇨ 等価な変形になっていなかった。
int from_to() const { return int(from_sq() + int(is_drop() ? (SQ_NB - 1) : 0)) * int(SQ_NB) + int(to_sq());}
⇨ これが正しいのか..
V8.35dev-b
-
diffとっておかしいところがないか調べる。
&& select<Next>([&]() { return cur->move() != refutations[0].move() && cur->move() != refutations[1].move() && cur->move() != refutations[2].move();
⇨ これで挙動変わるんか? ⇨ depth固定でbenchを計測する bench 1024 1 22 default depth ⇨ 変わってない。 これは原因調べるの大変っぽ…。
Visual Studio2つ起動して比較するか…。人力で..
bench 1024 1 1 default depth ⇨ 一致する bench 1024 1 2 default depth ⇨ 一致しない
PieceType move_dropped_piece() const { return PieceType((data >> 7) & 0x7f); }
int from_to() const { return int(from_sq()) + int(is_drop() ? (SQ_NB - 1) : 0) * int(SQ_NB) + int(to_sq()); }
これ変更したら値変わるようなった…なんぞこれ…。 assertは追加するんだよなー。
isready
position startpos moves 7g7f 8c8d 2g2f 8d8e 8h7g 3c3d 7i6h 4c4d 4g4f 4a3b 3i3h 3a4b 6i7h 4b4c 3h4g 7a6b 4g5f 9c9d 5i6i 5c5d 2f2e 2b3c 4i5i 7c7d 6i7i 6b5c 2h4h 6a6b 3g3f 8a7c 1g1f 5a6a 9g9f 1c1d 2i3g 5c6d 4f4e 4d4e 5f4e 3c7g+ 8i7g P*4g 4h4g B*3h 4g4f 3h2g+ 5i4h 6d5e 4f4g 8e8f 8g8f 8b8f 5g5f P*4f 4g5g 5e6d P*4d 4c5b P*8g 8f8b 5f5e 2a3c 4e3d 2g2f B*2a 2f4d 2a3b+ 4d3d G*3e 3d4c 3b4c 5b4c 5e5d P*5e B*2b 4c5d 2b3c+ 7c6e 5g5i 6e7g+ 6h7g N*5f 4h5h S*4h N*6f 5d6e N*8f 6b7c 3c4d 4h3g+ 5i4i 4f4g+ 5h4g P*4h 4g5f 4h4i+ 5f6e 6d6e 7i8i N*8e S*6b 8b6b 4d6b 6a6b R*8b 6b7a 8b8e+ R*5i N*7i B*4g P*5f 4g5f+ P*5g P*8h 8i8h 5i5g+ N*6h 5f4g P*5f S*6d 3e4d P*8d 8e6e 6d6e 7f7e 6e6f S*8b 7a8b 6g6f N*8e 7i6g R*6i S*7i 8e7g+
go perft 5
⇨ 一致した。
V8.35dev-a
- Moveクラス化 マージした。
=========================== Total time (ms) : 60006 Nodes searched : 65785646 Nodes_searched/second : 1096317
The bench command has completed. ⇨ 遅くはなってなさそう。 ⇨ R600ぐらい弱くなってる。どこかバグってるのか?diffとる。
The bench command has completed.
V8.35 base(dev) devブランチのソースコードにマージしていき、棋力を計測しなおす。
-
Move class化したのUnitTest通っていなかったの修正。
-
ビルドシステム、おかしいままビルドしてた。Moveをclass化したのがunittest通過してなかった。
evaldir ../build/nnue/eval bench
if (!pseudo_legal_check(pos, mlist_org, mlist))
{
cout << pos << endl;
for (auto l = mlist_org; l != mlist; ++l)
cout << *l << " " << pos.pseudo_legal(*l) << endl;
if (GenType == NON_CAPTURES || GenType == NON_CAPTURES_PRO_MINUS || GenType == NON_EVASIONS)
mlist = GenerateDropMoves<Us>()(pos, mlist_org, pos.empties());
for (auto l = mlist_org; l != mlist; ++l)
cout << *l << " " << pos.pseudo_legal(*l) << endl;
}
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834q.exe , eval = suisho10beta
T2,b1000,1422 - 186 - 1392(50.53% R3.7[-7.07,14.48]) winrate black , white = 52.52% , 47.48%
⇨ 悪化した。rollbackする。
V834q
-
qsearchのStep 1.~4. 改良
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834p.exe , eval = suisho10beta T2,b1000,1405 - 163 - 1432(49.52% R-3.31[-14.03,7.42]) winrate black , white = 51.85% , 48.15%
V834p
- qsearchのfutility pruning改良
V834o
-
LONG_EFFECT_LIBRARYを用いるコード、ビルド通っていなかったの修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834n.exe , eval = suisho10beta T2,b1000,1439 - 178 - 1383(50.99% R6.9[-3.86,17.65]) winrate black , white = 52.48% , 47.52% T2,b2000,1397 - 222 - 1381(50.29% R2.0[-8.84,12.84]) winrate black , white = 51.58% , 48.42%
V834n
- tanuki-mate等、ビルド通す。
V834m
- Move class導入。
- Move16のmember method追加。
V834l
- namespace CommandLine廃止
- CommandLineクラス追加
やねうらお — 今日 04:00
CommandLineがクラス化されとる。というか、Engineもクラス化されてるし namespace UCIは消滅してクラス化されとるし、OptionsMapもクラス化されとるし、エンジンオプション、以前はglobalに配置してたからOptions["..."]のようにアクセスできてたのに、Engineのメンバー変数になってしまったので、このEngineクラスのインスタンスをあちこちに持って回らないといけない。これに追随しようと思うと、ものすごい箇所の修正を同時に行う必要がある。下手すると1からStockfish探索部を移植しなおすぐらいの手間がある。(指し手生成等はできあがっているものとして)
過去最大級の修正。しかもこれをすべて反映させたところで、1㍉も強くならないという…。😢
どうするかよく考えよう..
https://github.com/yaneurao/YaneuraOu/commit/004ad63d2e3cb63bce47505a76b1cae92e09e750
V834k0
- engine.h / cpp追加
- usi_option.h追加
- namespace USI廃止
- namespace CommandLine廃止
- CommandLineクラス追加 ⇨ 変更箇所多すぎて動かんようなってしもて作業大変。これいったんrollbackする。
// 廃止になったコード
// --------------------
// USI関連
// --------------------
// Normalizes the internal value as reported by evaluate or search
// to the UCI centipawn result used in output. This value is derived from
// the win_rate_model() such that Stockfish outputs an advantage of
// "100 centipawns" for a position if the engine has a 50% probability to win
// from this position in self-play at fishtest LTC time control.
// evaluateまたはsearchによって報告される内部値をUSIの出力で使用されるUSIのcenti-pawnの値に正規化します
// この値はwin_rate_model()から派生しており、
// Stockfishがこのポジションから自己対局で50%の確率で勝利する場合、
// "100セントポーン"の利点を出力します。
// これは、fishtest LTCタイムコントロールでの自己対局においてです。
#if defined(USE_PIECE_VALUE)
// → やねうら王の場合、PawnValue = 90なので Value = 90なら 100として出力する必要がある。
// Stockfish 16ではこの値は328になっている。
const int NormalizeToPawnValue = Eval::PawnValue;
#endif
class Option;
/// The Option class implements each option as specified by the UCI protocol
// USIプロトコルで指定されるoptionの内容を保持するclass
class Option {
// USIプロトコルで"setoption"コマンドが送られてきたときに呼び出されるハンドラの型。
// typedef void(*OnChange)(const Option&);
// Stockfishでは↑のように関数ポインタになっているが、
// これだと[&](o){...}みたいなlambda式を受けられないのでここはstd::functionを使うべきだと思う。
using OnChange = void (*)(const Option&);
public:
// (GUI側のエンジン設定画面に出てくる)ボタン
Option(OnChange f = nullptr);
// 文字列
Option(const char* v, OnChange f = nullptr);
// (GUI側のエンジン設定画面に出てくる)CheckBox。bool型のoption デフォルト値が v
Option(bool v, OnChange f = nullptr);
// (GUI側のエンジン設定画面に出てくる)SpinBox。s64型。
// Stockfishではdouble型になっているけども、GUI側がdoubleを受け付けるようになっていない可能性があるし、
// doubleだと仮数部が52bitしかないので64bitの値を指定できなくて嫌だというのもある。
// ゆえに、doubleはサポートせずにs64のみを扱う。
Option(s64 v, s64 minv, s64 maxv, OnChange = nullptr);
// (GUI側のエンジン設定画面に出てくる)ComboBox。内容的には、string型と同等。
// list = コンボボックスに表示する値。v = デフォルト値かつ現在の値
// StockfishにはComboBoxの取扱いがないようなのだが、これは必要だと思うのでやねうら王では独自に追加する。
Option(const std::vector<std::string>&list, const std::string& v, OnChange f = nullptr);
// USIプロトコル経由で値を設定されたときにそれをcurrentValueに反映させる。
Option& operator=(const std::string&);
// 起動時に設定を代入する。
void operator<<(const Option&);
// s64型への暗黙の変換子。
// Stockfishでは、intになっているが、やねうら王ではs64に拡張している。
operator s64() const;
// string型への暗黙の変換子
// typeが"string"型のとき以外であっても何であれ変換できるようになっているほうが便利なので
// 変換できるようにしておく。
operator std::string() const;
// case insensitiveにしないといけないので比較演算子は独自に用意する。
bool operator==(const char*) const;
// idxの値を変えずに上書きする。
// ※ やねうら王、独自拡張。
// コマンド文字列からOptionのインスタンスを構築する時にこの機能が必要となる。
void overwrite(const Option&);
// 既存のOptionの上書き。
// min = max = default = param になる。
void overwrite(const std::string& param);
private:
friend std::ostream& operator<<(std::ostream& os, const OptionsMap& om);
std::string defaultValue, currentValue, type;
// s64型のときの最小と最大
// Stockfishではintになっているが、node limitなどs64の範囲の値を扱いたいのでやねうら王では拡張してある。
s64 min, max;
// 出力するときの順番。この順番に従ってGUIの設定ダイアログに反映されるので順番重要!
size_t idx;
// combo boxのときの表示する文字列リスト
std::vector<std::string> list;
// 値が変わったときに呼び出されるハンドラ
OnChange on_change;
};
// optionのdefault値を設定する。
void init(OptionsMap&);
// USIメッセージ応答部(起動時に、各種初期化のあとに呼び出される)
void loop(int argc, char* argv[]);
#if defined(USE_PIECE_VALUE)
// Valueをcp(centi-pawn)に変換する。
int to_cp(Value v);
// cpからValueへ。⇑の逆変換。
Value cp_to_value(int v);
// USIプロトコルの形式でValue型を出力する。
// 歩が100になるように正規化するので、operator <<(Value)をこういう仕様にすると
// 実際の値と異なる表示になりデバッグがしにくくなるから、そうはしていない。
// USE_PIECE_VALUEが定義されていない時は正規化しようがないのでこの関数は呼び出せない。
std::string value(Value v);
#endif
// Square型をUSI文字列に変換する
std::string square(Square s);
// 指し手をUSI文字列に変換する。
std::string move(Move m /*, bool chess960*/);
std::string move(Move16 m /*, bool chess960*/);
// 読み筋をUSI文字列化して返す。
// " 7g7f 8c8d" のように返る。
std::string move(const std::vector<Move>& moves);
// 局面posとUSIプロトコルによる指し手を与えて
// もし可能なら等価で合法な指し手を返す。
// 合法でないときはMOVE_NONEを返す。(この時、エラーである旨を出力する。)
// "resign"に対してはMOVE_RESIGNを返す。
// Stockfishでは第二引数にconstがついていないが、これはつけておく。
// 32bit Moveが返る。(Move16ではないことに注意)
Move to_move(const Position& pos, const std::string& str);
// -- 以下、やねうら王、独自拡張。
// 合法かのテストはせずにともかく変換する版。
// 返ってくるのは16bitのMoveなので、これを32bitのMoveに変換するには
// Position::move16_to_move()を呼び出す必要がある。
// Stockfishにはない関数だが、高速化を要求されるところで欲しいので追加する。
Move16 to_move16(const std::string& str);
// USIプロトコルで、idxの順番でoptionを出力する。(デバッグ用)
std::ostream& operator<<(std::ostream& os, const OptionsMap& om);
// USIに追加オプションを設定したいときは、この関数を定義すること。
// USI::init()のなかからコールバックされる。
void extra_option(OptionsMap& o);
// 評価関数を読み込んだかのフラグ。これはevaldirの変更にともなってfalseにする。
extern bool load_eval_finished; // = false;
#if defined (USE_ENTERING_KING_WIN)
// 入玉ルール文字列をEnteringKingRule型に変換する。
EnteringKingRule to_entering_king_rule(const std::string& rule);
#endif
// エンジンオプションをコンパイル時に設定する機能
// "ENGINE_OPTIONS"で指定した内容を設定する。
// 例) #define ENGINE_OPTIONS "FV_SCALE=24;BookFile=no_book"
void set_engine_options(const std::string& options);
// エンジンオプションのoverrideのためにファイルから設定を読み込む。
// 1) これは起動時に"engine_options.txt"という設定ファイルを読み込むのに用いる。
// 2) "isready"応答に対して、EvalDirのなかにある"eval_options.txt"という設定ファイルを読み込むのにも用いる。
void read_engine_options(const std::string& filename);
namespace USI {
void UnitTest(Test::UnitTester& tester);
}
// USIのoption設定はここに保持されている。
extern OptionsMap Options;
// === やねうら王独自実装 ===
// USIの"isready"コマンドが呼び出されたときの処理。このときに評価関数の読み込みなどを行なう。
// benchmarkコマンドのハンドラなどで"isready"が来ていないときに評価関数を読み込ませたいときに用いる。
// skipCorruptCheck == trueのときは評価関数の2度目の読み込みのときのcheck sumによるメモリ破損チェックを省略する。
// ※ この関数は、Stockfishにはないがないと不便なので追加しておく。
void is_ready(bool skipCorruptCheck = false);
// positionコマンドのparserを呼び出したいことがあるので外部から呼び出せるようにしておく。
// 使い方はbenchコマンド(benchmark.cpp)のコードを見てほしい。
void position_cmd(Position& pos, std::istringstream& is, StateListPtr& states);
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834j.exe , eval = suisho10beta
T2,b1000,1386 - 176 - 1438(49.08% R-6.4[-17.15,4.36]) winrate black , white = 52.55% , 47.45%
T2,b2000,1370 - 230 - 1400(49.46% R-3.76[-14.62,7.09]) winrate black , white = 51.95% , 48.05%
T2,b4000,903 - 194 - 943(48.92% R-7.53[-20.83,5.77]) winrate black , white = 53.41% , 46.59%
V8.34j
- ValueList追加。
- tt.cpp、Ubuntuでビルド通らなかったの修正。
// ----------------------------------------------------------------------------------------------------------
// 探索スレッドごとに個別の置換表へのアクセス
// ----------------------------------------------------------------------------------------------------------
// 以下のTT.probe()は、学習用の実行ファイルではスレッドごとに持っているTTのほうにアクセスして欲しいので、
// TTのマクロを定義して無理やりそっちにアクセスするように挙動を変更する。
#if defined(EVAL_LEARN)
#define TT (thisThread->tt)
// Threadのメンバにttという変数名で、スレッドごとのTranspositionTableを持っている。
// そちらを参照するように変更する。
#endif
// ----------------------------------------------------------------------------------------------------------
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834i.exe , eval = suisho10beta
T2,b1000,1424 - 189 - 1387(50.66% R4.57[-6.2,15.35]) winrate black , white = 52.22% , 47.78%
T2,b2000,1397 - 237 - 1366(50.56% R3.9[-6.97,14.77]) winrate black , white = 51.72% , 48.28%
V8.34i
- HASH_KEY_BITS 128に変更したときにビルドが通らなかったの修正。
- tt.cppにHASH_KEY_BITSが128,256の時のコード追加。
- tt.cppのfirst_entry()のコードが将棋用のコードになっていなかったの修正。
- wasm用のビルドができなかったの修正。
V8.34h
-
ttMove削除。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834g.exe , eval = suisho10beta T2,b1000,179 - 24 - 187(48.91% R-7.6[-37.44,22.25]) winrate black , white = 53.01% , 46.99%
V8.34g
-
TTData導入。
-
extract_ponder_from_ttでiteration 2回目で取得したponder設定するのやめる。(こんなの該当するケースが稀)
-
TT.probe()でTTの内部状態が変更されないことは保証されるようになった。
-
USI::pv() → searchのほうに移動
-
TODO : LEARN版のTTの初期化と確保のコード、あとで書く。
-
TODO : 色々壊してしまっているのでむっちゃ弱い。修正作業中。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834f.exe , eval = suisho10beta T2,b1000,454 - 54 - 492(47.99% R-13.96[-32.55,4.62]) winrate black , white = 53.91% , 46.09% T2,b2000,225 - 37 - 208(51.96% R13.65[-13.81,41.1]) winrate black , white = 50.81% , 49.19%
やねうらお — 今日 20:15
Stockfishのtt.h / tt.cppをportingしてきた。探索部のほうも大量に変更が必要になった。
TTをglobalに確保するコードを用意しないといけない。これはまだ。これをやって対局させて問題なければ、GitHubに反映させる。
今回の改良により、やねうら王の探索部がかなりglobalなobjectに依存している部分が削減されるので、もう少し改造すれば、普通にふかうら王とやねうら王とを共存させる(同時に同じプロセス空間内で動作させる)ことも不可能ではなさそう。
とりあえず、tt.h / tt.cppの大改造は、本日、あとちょっと頑張ればたぶん動きそう..。
それで、numa.hを使うようにする件なのだが、これは変更箇所が多く、まだまだ時間かかりそう。これ来週以降にするかー。これやってるとふかうら王News Letterがいつまで経っても発行できんのよね…。
あと、LEARN版をなくす件、確かに技術的には無くせそうなのだけど、NNUEのLEARN版の実行ファイルはOpenBlasに依存してて、これの.libファイルを付属させて配布しないといけないのはちょっと嫌なので、LEARN版を無くすの自体は、やっぱり保留にしようかと…。
// Indicate PvNodes that will probably fail low if the node was searched
// at a depth equal to or greater than the current depth, and the result
// of this search was a fail low.
// 現在の深さと同じかそれ以上の深さでノードが探索され、
// その探索の結果がfail lowだった場合、おそらくfail lowになるであろうPvNodesを示す。
// ノードが現在のdepth以上で探索され、fail lowである時に、PvNodeがfail lowしそうであるかを示すフラグ。
bool likelyFailLow = PvNode
&& ttMove
&& (tte->bound() & BOUND_UPPER)
&& tte->depth() >= depth;
// --------------------
// 置換表
// --------------------
/// 置換表エントリー
/// 本エントリーは10bytesに収まるようになっている。3つのエントリーを並べたときに32bytesに収まるので
/// CPUのcache lineに一発で載るというミラクル。
///
/// ※ cache line sizeは、IntelだとPentium4やPentiumMからでPentiumⅢ(3)までは32byte。
/// そこ以降64byte。AMDだとK8のときには既に64byte。
///
/// key 16 bit : hash keyの下位16bit(bit0は除くのでbit16..1)
/// depth 8 bit : 格納されているvalue値の探索深さ
/// move 16 bit : このnodeの最善手(指し手16bit ≒ Move16 , Moveの上位16bitは無視される)
/// generation 5 bit : このエントリーにsave()された時のTTの世代カウンターの値
/// pv node 1 bit : PV nodeで調べた値であるかのフラグ
/// bound type 2 bit : 格納されているvalue値の性質(fail low/highした時の値であるだとか)
/// value 16 bit : このnodeでのsearch()の返し値
/// eval value 16 bit : このnodeでのevaluate()の返し値
struct TTEntry {
Move16 move() const { return Move16(move16); }
Value value() const { return Value(value16); }
Value eval() const { return Value(eval16 ); }
Depth depth() const { return Depth(depth8 + DEPTH_ENTRY_OFFSET); }
bool is_pv() const { return bool (genBound8 & 0x4); }
Bound bound() const { return Bound(genBound8 & 0x3); }
// 置換表のエントリーに対して与えられたデータを保存する。上書き動作
// v : 探索のスコア
// ev : 評価関数 or 静止探索の値
// pv : PV nodeであるか
// d : その時の探索深さ
// m : ベストな指し手
// ※ KeyとしてKey(64 bit)以外に 128,256bitのhash keyにも対応。(やねうら王独自拡張)
void save(Key k, Value v, bool pv , Bound b, Depth d, Move m, Value ev);
void save(Key128& k, Value v, bool pv , Bound b, Depth d, Move m, Value ev);
void save(Key256& k, Value v, bool pv , Bound b, Depth d, Move m, Value ev);
uint8_t relative_age(const uint8_t generation8) const;
// -- やねうら王独自拡張
// やねうら王では、TTClusterSizeを変更できて、これが2の時は、TTEntryに格納するhash keyは64bit。(Stockfishのように)3の時は16bit。
#if TT_CLUSTER_SIZE == 3
typedef uint16_t KEY_TYPE;
#else // TT_CLUSTER_SIZEが2,4,6,8の時は64bit。5,7は選択する意味がないと思うので考えない。
typedef uint64_t KEY_TYPE;
#endif
private:
friend class TranspositionTable;
// save()の内部実装用
void save_(TTEntry::KEY_TYPE key_for_ttentry, Value v, bool pv , Bound b, Depth d, Move m, Value ev);
// hash keyの下位bit16(bit0は除く)
// Stockfishの最新版[2020/11/03]では、key16はhash_keyの下位16bitに変更になったが(取り出しやすいため)
// やねうら王ではhash_keyのbit0を先後フラグとして用いるので、bit16..1を使う。
// hash keyの上位bitは、TTClusterのindexの算出に用いるので、下位を格納するほうが理にかなっている。
TTEntry::KEY_TYPE key;
// 指し手(の下位16bit。Moveの上位16bitには移動させる駒種などが格納される)
uint16_t move16;
// このnodeでのsearch()の値
int16_t value16;
// このnodeでのevaluate()の値
int16_t eval16;
// entryのgeneration上位5bit + PVであるか1bit + Bound下位2bitのpackしたもの。
// generationはエントリーの世代を表す。TranspositionTableで新しい探索ごとに+8されていく。
uint8_t genBound8;
// そのときの残り深さ(これが大きいものほど価値がある)
// 1バイトに収めるために、DepthをONE_PLYで割ったものを格納する。
// 符号付き8bitだと+127までしか表現できないので、符号なしにして、かつ、
// DEPTH_NONEが-6なのでこの分だけ下駄履きさせてある。(+6して格納してある)
uint8_t depth8;
};
// コピペ用。あとで消す。
#if defined(EVAL_LEARN)
// スレッド数が変更になった時にThread.set()から呼び出される。
// これに応じて、スレッドごとに保持しているTTを初期化する。
void init_tt_per_thread();
#endif
// 確保されたメモリの先頭(alignされていない)
//void* mem;
// → やねうら王では、LargeMemoryで確保するのでこれは不要
// --- やねうら王独自拡張
// probe()の内部実装用。
// key_for_index : first_entry()で使うためのkey
// key_for_ttentry : TTEntryに格納するためのkey
TTEntry* probe (const Key key_for_index, const TTEntry::KEY_TYPE key_for_ttentry, bool& found) const;
// 作業中
void TTEntry::save_(TTEntry::KEY_TYPE key_for_ttentry, Value v, bool pv, Bound b, Depth d, Move m, Value ev)
{
// ASSERT_LV3((-VALUE_INFINITE < v && v < VALUE_INFINITE) || v == VALUE_NONE);
// 置換表にVALUE_INFINITE以上の値を書き込んでしまうのは本来はおかしいが、
// 実際には置換表が衝突したときにqsearch()から書き込んでしまう。
//
// 例えば、3手詰めの局面で、置換表衝突により1手詰めのスコアが返ってきた場合、VALUE_INFINITEより
// 大きな値を書き込む。
//
// 逆に置換表をprobe()したときにそのようなスコアが返ってくることがある。
// しかしこのようなスコアは、mate distance pruningで補正されるので問題ない。
// (ように、探索部を書くべきである。)
//
// Stockfishで、VALUE_INFINITEを32001(int16_tの最大値よりMAX_PLY以上小さな値)にしてあるのは
// そういった理由から。
// このif式だが、
// A = m!=MOVE_NONE
// B = ((u16)(k >> 1)) != key16)
// として、ifが成立するのは、
// a) A && !B
// b) A && B
// c) !A && B
// の3パターン。b),c)は、B == trueなので、その下にある次のif式が成立して、この局面のhash keyがkey16に格納される。
// a)は、B == false すなわち、((u16)(k >> 1)) == key16であり、この局面用のentryであるから、その次のif式が成立しないとしても
// 整合性は保てる。
// a)のケースにおいても、指し手の情報は格納しておいたほうがいい。
// これは、このnodeで、TT::probeでhitして、その指し手は試したが、それよりいい手が見つかって、枝刈り等が発生しているような
// ケースが考えられる。ゆえに、今回の指し手のほうが、いまの置換表の指し手より価値があると考えられる。
// Preserve the old ttmove if we don't have a new one
if (m || key_for_ttentry != key)
move16 = uint16_t(m);
// このエントリーの現在の内容のほうが価値があるなら上書きしない。
// 1. hash keyが違うということはTT::probeでここを使うと決めたわけだから、このEntryは無条件に潰して良い
// 2. hash keyが同じだとしても今回の情報のほうが残り探索depthが深い(新しい情報にも価値があるので
// 少しの深さのマイナスなら許容)
// 3. BOUND_EXACT(これはPVnodeで探索した結果で、とても価値のある情報なので無条件で書き込む)
// 1. or 2. or 3.
if (b == BOUND_EXACT
|| key_for_ttentry != key
|| d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4)
// ここ、 2 * pv を入れたほうが強いらしい。
// https://github.com/official-stockfish/Stockfish/commit/94514199123874c0029afb6e00634f26741d90db
{
ASSERT_LV3(d > DEPTH_ENTRY_OFFSET);
ASSERT_LV3(d < 256 + DEPTH_ENTRY_OFFSET);
key = key_for_ttentry;
depth8 = uint8_t(d - DEPTH_ENTRY_OFFSET); // DEPTH_ENTRY_OFFSETだけ下駄履きさせてある。
genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b);
value16 = int16_t(v);
eval16 = int16_t(ev);
}
}
// 置換表のサイズを確保しなおす。
void TranspositionTable::resize(size_t mbSize) {
#if defined(TANUKI_MATE_ENGINE) || defined(YANEURAOU_MATE_ENGINE) || defined(YANEURAOU_ENGINE_DEEP)
// これらのエンジンでは、この置換表は用いないので確保しない。
return;
#endif
// Optionのoverrideによってスレッド初期化前にハンドラが呼び出された。これは無視する。
if (Threads.size() == 0)
return;
// 探索が終わる前に次のresizeが来ると落ちるので探索の終了を待つ。
Threads.main()->wait_for_search_finished();
// mbSizeの単位は[MB]なので、ここでは1MBの倍数単位のメモリが確保されるが、
// 仕様上は、1MBの倍数である必要はない。
size_t newClusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
// clusterCountは偶数でなければならない。
// この理由については、TTEntry::first_entry()のコメントを見よ。
// しかし、1024 * 1024 / sizeof(Cluster)の部分、sizeof(Cluster)==64なので、
// これを掛け算するから2の倍数である。
ASSERT_LV3((newClusterCount & 1) == 0);
// 同じサイズなら確保しなおす必要はない。
// Stockfishのコード、問答無用で確保しなおしてゼロクリアしているが、
// ゼロクリアの時間も馬鹿にならないのであまり良いとは言い難い。
if (newClusterCount == clusterCount)
return;
aligned_large_pages_free(table);
clusterCount = newClusterCount;
// tableはCacheLineSizeでalignされたメモリに配置したいので、CacheLineSize-1だけ余分に確保する。
// callocではなくmallocにしないと初回の探索でTTにアクセスするとき、特に巨大なTTだと
// 極めて遅くなるので、mallocで確保して自前でゼロクリアすることでこれを回避する。
// cf. Explicitly zero TT upon resize. : https://github.com/official-stockfish/Stockfish/commit/2ba47416cbdd5db2c7c79257072cd8675b61721f
// Large Pageを確保する。ランダムメモリアクセスが5%程度速くなる。
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
// clear();
// → Stockfish、ここでclear()呼び出しているが、Search::clear()からTT.clear()を呼び出すので
// 二重に初期化していることになると思う。
#if defined(EVAL_LEARN)
// スレッドごとにTTを持つ実装なら、確保しているメモリサイズが変更になったので、
// スレッドごとのTTを初期化してやる必要がある。
init_tt_per_thread();
#endif
}
void TranspositionTable::clear()
{
#if defined(TANUKI_MATE_ENGINE) || defined(YANEURAOU_MATE_ENGINE)
// MateEngineではこの置換表は用いないのでクリアもしない。
return;
#endif
generation8 = 0;
auto size = clusterCount * sizeof(Cluster);
// 進捗を表示しながら並列化してゼロクリア
// Stockfishのここにあったコードは、独自の置換表を実装した時にも使いたいため、tt.cppに移動させた。
Tools::memclear("USI_Hash", table, size);
}
// probe()の内部実装用。
// key_for_index : first_entry()で使うためのkey
// key_for_ttentry : TTEntryに格納するためのkey
TTEntry* TranspositionTable::probe(const Key key_for_index, const TTEntry::KEY_TYPE key_for_ttentry , bool& found) const
{
ASSERT_LV3(clusterCount != 0);
#if defined(USE_GLOBAL_OPTIONS)
if (!GlobalOptions.use_hash_probe)
{
// 置換表にhitさせないモードであるなら、見つからなかったことにして
// つねに確保しているメモリの先頭要素を返せば良い。(ここに書き込まれたところで問題ない)
return found = false, first_entry(0);
}
#endif
// 最初のTT_ENTRYのアドレス(このアドレスからTT_ENTRYがClusterSize分だけ連なっている)
// keyの下位bitをいくつか使って、このアドレスを求めるので、自ずと下位bitはいくらかは一致していることになる。
TTEntry* const tte = first_entry(key_for_index);
// クラスターのなかから、keyが合致するTT_ENTRYを探す
for (int i = 0; i < ClusterSize; ++i)
{
// returnする条件
// 1. 空のエントリーを見つけた(そこまではkeyが合致していないので、found==falseにして新規TT_ENTRYのアドレスとして返す)
// 2. keyが合致しているentryを見つけた。(found==trueにしてそのTT_ENTRYのアドレスを返す)
// Stockfishのコードだと、1.が成立したタイミングでもgenerationのrefreshをしているが、
// save()のときにgenerationを書き出すため、このケースにおいてrefreshは必要ない。
// (しかしソースコードをStockfishに合わせておくことに価値があると思うので、Stockfishに合わせておく)
// Stockfish11まではkey16 == 0が空のTTEntryを意味することになっていたが、
// Stockfish12からはdepth8 == 0が空のTTEntryを意味するように変わった。
// key16は1/65536の確率で0になりうるので…。
if (tte[i].key == key_for_ttentry)
{
tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh
return found = bool(tte[i].depth8), &tte[i];
}
}
// 空きエントリーも、探していたkeyが格納されているentryが見当たらなかった。
// クラスター内のどれか一つを潰す必要がある。
TTEntry* replace = tte;
for (int i = 1; i < ClusterSize; ++i)
// ・深い探索の結果であるものほど価値があるので残しておきたい。すなわち、depth8 が高いほど良い探索結果だから残しておきたい。
// ・generationがいまの探索generationに近いものほど価値があるので残しておきたい。depth8 - generation のようにする。
// gerationは、次の局面が来るごとに8ずつ増える。普通は2手先の局面が来る。
// (新規対局時にはTTを丸ごとクリアしているから新規対局時のことは考えなくて良い)
// すなわち2手前の局面の探索結果については、depth8 - 8 の深さで探索した結果であるとみなす。(depth8 - 2でもいいような気は少しするが、
// この情報が現局面から到達可能な局面の情報とは限らないので、少し大きめのペナルティを加えているのだと思う。)
//
// 以上に基いてスコアリングする。
// 以上の合計が一番小さいTTEntryを使う。
//
// 詳しくは、以下のブログ記事に書いた。
// https://yaneuraou.yaneu.com/2023/06/09/replacement-strategy-in-transposition-table/
//
if (replace->depth8 - replace->relative_age(generation8) * 2
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
replace = &tte[i];
return found = false, replace;
}
// read onlyであることが保証されているprobe()
TTEntry* TranspositionTable::read_probe(const Key key_for_index, const TTEntry::KEY_TYPE key_for_ttentry , bool& found) const
{
ASSERT_LV3(clusterCount != 0);
#if defined(USE_GLOBAL_OPTIONS)
if (!GlobalOptions.use_hash_probe)
return found = false, first_entry(0);
#endif
TTEntry* const tte = first_entry(key_for_index);
for (int i = 0; i < ClusterSize; ++i)
{
if (tte[i].key == key_for_ttentry || !tte[i].depth8)
return found = (bool)tte[i].depth8, &tte[i];
}
return found = false, nullptr;
}
// やねうら王独自拡張
//
// hash keyを64bit,128bit,256bitに自由に変更できる。 → config.h で HASH_KEY_BITS を設定する。
// またTTClusterSizeを2または3を選択できる。
//
// TTClusterSizeとして2を選択した場合、TTEntryに格納されるhash keyは64bitになる。
// TTClusterSizeとして3を選択した場合、TTEntryに格納されるhash keyは16bitになる。
// → config.h で TTClusterSize を設定する。
void TTEntry::save(Key k, Value v, bool pv , Bound b, Depth d, Move m, Value ev) { save_((TTEntry::KEY_TYPE)(k >> 1) ,v, pv, b, d, m, ev);}
void TTEntry::save(Key128& k, Value v, bool pv , Bound b, Depth d, Move m, Value ev) { save_((TTEntry::KEY_TYPE) k.extract64<1>() ,v, pv, b, d, m, ev);}
void TTEntry::save(Key256& k, Value v, bool pv , Bound b, Depth d, Move m, Value ev) { save_((TTEntry::KEY_TYPE) k.extract64<1>() ,v, pv, b, d, m, ev);}
TTEntry* TranspositionTable::probe (const Key key, bool& found) const { return probe(key ,(TTEntry::KEY_TYPE)(key >> 1 ), found); }
TTEntry* TranspositionTable::probe (const Key128& key, bool& found) const { return probe(key.extract64<0>(),(TTEntry::KEY_TYPE)(key.extract64<1>()), found); }
TTEntry* TranspositionTable::probe (const Key256& key, bool& found) const { return probe(key.extract64<0>(),(TTEntry::KEY_TYPE)(key.extract64<1>()), found); }
TTEntry* TranspositionTable::read_probe (const Key key, bool& found) const { return read_probe(key ,(TTEntry::KEY_TYPE)(key >> 1 ), found); }
TTEntry* TranspositionTable::read_probe (const Key128& key, bool& found) const { return read_probe(key.extract64<0>(),(TTEntry::KEY_TYPE)(key.extract64<1>()), found); }
TTEntry* TranspositionTable::read_probe (const Key256& key, bool& found) const { return read_probe(key.extract64<0>(),(TTEntry::KEY_TYPE)(key.extract64<1>()), found); }
int TranspositionTable::hashfull() const
{
// すべてのエントリーにアクセスすると時間が非常にかかるため、先頭から1000エントリーだけ
// サンプリングして使用されているエントリー数を返す。
// Stockfish11では、1000 Cluster(3000 TTEntry)についてサンプリングするように変更されたが、
// 計測時間がもったいないので、古いコードのままにしておく。
int cnt = 0;
for (int i = 0; i < 1000 / ClusterSize; ++i)
for (int j = 0; j < ClusterSize; ++j)
cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
// return cnt;でも良いが、そうすると最大で999しか返らず、置換表使用率が100%という表示にならない。
return cnt * 1000 / (ClusterSize * (1000 / ClusterSize));
}
#if defined(EVAL_LEARN)
// スレッド数が変更になった時にThread.set()から呼び出される。
// これに応じて、スレッドごとに保持しているTTを初期化する。
void TranspositionTable::init_tt_per_thread()
{
// スレッド数
size_t thread_size = Threads.size();
// エンジン終了時にThreads.set(0)で全スレッド終了させるコードが書いてあるので、
// そのときに、Threads.size() == 0の状態で呼び出される。
// ここで抜けないと、このあとゼロ除算することになる。
if (thread_size == 0)
return;
// 1スレッドあたりのクラスター数(端数切捨て)
// clusterCountは2の倍数でないと駄目なので、端数を切り捨てるためにLSBを0にする。
size_t clusterCountPerThread = (clusterCount / thread_size) & ~(size_t)1;
ASSERT_LV3((clusterCountPerThread & 1) == 0);
// これを、自分が確保したglobalな置換表用メモリから切り分けて割当てる。
for (size_t i = 0; i < thread_size; ++i)
{
auto& tt = Threads[i]->tt;
tt.clusterCount = clusterCountPerThread;
tt.table = this->table + clusterCountPerThread * i;
}
}
やねうらお — 今日 13:06
■ TTがglobalでなくなる件
TT(Transposition Table = 置換表)、最新のStockfishでは、Workerというclass(これは探索スレッドを表現している)がTTの参照を持っているので、global objectであるTTのインスタンスを直接参照しなくて良くなった。
これは何を意味するかと言うと、従来、TTをスレッドごとに持つためにLEARN版を用意していたのだが、TTをスレッドごとに持つというのも、Workerごとに異なるTTのインスタンスを参照するようにすればいいということである。
つまりは、この改造をうまく適用するとLEARN版はもう要らなくなるということである。
しかし探索用のobjectが全部Workerというclassに集約されるため、大改造になる。
これがとても大変な改造となっている。🥺 これうまく取り込むだけで一週間ぐらいかかりかねない。
やねうらお — 今日 13:48
この変更に伴い、Stockfishではtt.h / tt.cppが大改造になっている。
あと、以前はTTEntryに書き込むときに世代(TT.generation)は、TTから取得していたのに引数で渡すように変更になっている。
つまり、スレッドごとに異なる置換表(異なる置換表なので異なる世代である)に書き込むことが可能なようになっている。やねうら王では以前から教師生成時にスレッドごとに異なる局面を探索させるためにそのように変更していたのだが、Stockfishがこれに追随したようである。
Stockfishで教師生成をどうやっているのか知らないのだけども、以前はおそらくたぬきさんがStockfishを独自改造したソースコードで教師生成していたのだと思う。それが、Stockfish本体で教師生成ができるようにしようとしている(現状、Stockfish本体で教師生成できる?)のだと思う。
-
movepick.cppのコメントをソースコードに合わせて書き直した。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834e.exe , eval = suisho10beta T2,b1000,1428 - 179 - 1393(50.62% R4.31[-6.45,15.07]) winrate black , white = 51.79% , 48.21% T2,b2000,1368 - 260 - 1372(49.93% R-0.51[-11.42,10.41]) winrate black , white = 51.39% , 48.61% T2,b4000,822 - 203 - 825(49.91% R-0.63[-14.71,13.45]) winrate black , white = 55.19% , 44.81% T2,b4000,867 - 179 - 814(51.58% R10.96[-2.98,24.9]) winrate black , white = 53.48% , 46.52%
V8.34e
- qsearch()のsaveでdepth、初期化していないかったの修正。⇨ DEPTH_QS。
- qsearch()のttDepth除去。
- LEARN版のビルドが通っていなかったの修正。
やねうらお — 今日 03:12 ■ Killer Moveは、いらん子だった?!
Killer MoveもCounter Moveもなくなったのに強さ以前と変わらないのだ…。
ついに、統計情報に基づく指し手のordering性能が上がりすぎたため、killer MoveもCounter Moveも、ないほうが良いということになったのだ…。
これは、どえらいことなのだ…。🙄
V8.34d
-
LowPlyHistory class作成。
-
LowPlyHistoryの初期化忘れ修正。
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834c.exe , eval = suisho10beta T2,b1000,251 - 32 - 267(48.46% R-10.74[-35.83,14.36]) winrate black , white = 54.25% , 45.75%
V8.34c
- MovePicker丸ごと差し替え。
- LowPlyHistory導入。
- Search::Stack::killers廃止
- update_quiet_stats() ⇨ update_quiet_histories()に変更。
- DEPTH_QS_NORMAL、DEPTH_QS_RECAPTURES廃止。
// 静止探索で王手がかかっていないとき。
DEPTH_QS_NORMAL = -1,
// 静止探索でこれより深い(残り探索深さが少ない)ところではRECAPTURESしか生成しない。
DEPTH_QS_RECAPTURES = -5,
// 王手がかかっているなら王手回避のフェーズへ。さもなくばQSEARCHのフェーズへ。
// stage = (pos.in_check() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
// ⇨ Stockfish 16のコード、ttm(置換表の指し手)は無条件でこのMovePickerが返す1番目の指し手としているが、これだと
// TTの指し手だけで千日手になってしまうことがある。これは、将棋ではわりと起こりうる。
// 対策としては、qsearchで千日手チェックをしたり、SEEが悪いならskipするなど。
// ※ ここでStockfish 14のころのように置換表の指し手に条件をつけるのは良くなさげ。(V7.74l3 と V7.74mとの比較)
// → ただし、その場合、qsearch()で千日手チェックが必要になる。
// qsearchでの千日手チェックのコストが馬鹿にならないので、
// ⇓このコードを有効にして、qsearch()での千日手チェックをやめた方が得。
// Stockfish 14のコード
stage = (pos.in_check() ? EVASION_TT : QSEARCH_TT) +
!(ttm
&& (pos.in_check() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
&& pos.pseudo_legal(ttm));
// If we found no move and the depth is too low to try checks, then we have finished
// 指し手がなくて、depthがDEPTH_QS_NORMALより深いなら、これで終了
// depth == DEPTH_QS_NORMAL + 1 == DEPTH_QS_CHECKS のときは特別に
// 王手になる指し手も生成する。
if (depth <= DEPTH_QS_NORMAL)
return MOVE_NONE;
++stage;
[[fallthrough]];
// 王手となる指し手の生成
case QCHECK_INIT:
// この前のフェーズでCAPTURES_PRO_PLUSで生成していたので、駒を取らない王手の指し手生成(QUIET_CHECKS) - 歩の成る指し手の除外 が必要。
// (歩の成る指し手は王手であろうとすでに生成して試したあとである)
// QUIET_CHECKS_PRO_MINUSがあれば良いのだが、実装が難しいので、QUIET_CHECKSで生成して、このあとQCHECK_で歩の成る指し手を除外する。
cur = moves;
//endMoves = Search::Limits.generate_all_legal_moves ? generateMoves<QUIET_CHECKS_ALL>(pos, cur) : generateMoves<QUIET_CHECKS>(pos, cur);
// → qsearch()なので歩の成らずは生成しなくてもいいや..
endMoves = generateMoves<QUIET_CHECKS>(pos, cur);
++stage;
[[fallthrough]];
// 王手になる指し手を一手ずつ返すフェーズ
case QCHECK:
return select<Next>([](){ return true; });
//return select<Next>([&]() { return !pos.pawn_promotion(*cur); });
// 王手する指し手は、即詰みを狙うものであり、駒捨てとかがあるからオーダリングが難しく、効果に乏しいのでオーダリングしない。
if (cutNode)
r += 2 - (tte->depth() >= depth && ss->ttPv)
+ (!ss->ttPv && move != ttMove && move != ss->killers[0]);
↓
if (cutNode)
r += 2 - (tte->depth() >= depth && ss->ttPv);
Move killers[2]; // killer move
// Update killers
// killerの指し手のupdate
// killer 2本しかないので[0]と違うならいまの[0]を[1]に降格させて[0]と差し替え
if (ss->killers[0] != move)
{
ss->killers[1] = ss->killers[0];
ss->killers[0] = move;
}
⇨ 廃止
// update_quiet_stats() updates move sorting heuristics
// update_quiet_stats()は、新しいbest moveが見つかったときに指し手の並べ替えheuristicsを更新する。
// 具体的には駒を取らない指し手のstat tables、killer等を更新する。
// move = これが良かった指し手
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus)
{
// historyのupdate
Color us = pos.side_to_move();
Thread* thisThread = pos.this_thread();
thisThread->mainHistory(us, from_to(move)) << bonus;
update_continuation_histories(ss, pos.moved_piece_after(move), to_sq(move), bonus);
// Update countermove history
if (is_ok((ss - 1)->currentMove))
{
// 直前に移動させた升(その升に移動させた駒がある。今回の指し手はcaptureではないはずなので)
Square prevSq = to_sq((ss - 1)->currentMove);
thisThread->counterMoves(pos.piece_on(prevSq), prevSq) = move;
}
}
⇨ 書き換え
engine1 = YaneuraOuNNUE_halfkp_512x2_8_64_V834base.exe , eval = suisho10beta
engine2 = YaneuraOuNNUE_halfkp_512x2_8_64_V834b.exe , eval = suisho10beta
T2,b1000,342 - 44 - 344(49.85% R-1.01[-22.82,20.79]) winrate black , white = 52.92% , 47.08%
T2,b1000,420 - 56 - 414(50.36% R2.5[-17.28,22.28]) winrate black , white = 49.88% , 50.12%
T2,b2000,228 - 31 - 221(50.78% R5.42[-21.53,32.36]) winrate black , white = 50.11% , 49.89%
T2,b2000,239 - 26 - 225(51.51% R10.49[-16.03,37.0]) winrate black , white = 49.78% , 50.22%
⇨ R10程度弱くなったかどうかなので、これはまあ、良しとする。
V8.34b
- multipleExtensions廃止
- ふかうら王でモデルファイルが存在しないときにFileOpenErrorではなくFileNotFoundを返すように修正。
int multipleExtensions; // 前のノードで延長した手数と今回のノードで延長したか手数を加算した値
// 置換表の指し手以外がすべてfail lowしているならsingular延長確定。
if (value < singularBeta)
{
extension = 1;
// We make sure to limit the extensions in some way to avoid a search explosion
// 2重延長を制限することで探索の組合せ爆発を回避する。
// TODO : ここのパラメーター、調整すべきかも?
if (!PvNode && value < singularBeta - 2 && ss->multipleExtensions <= 15)
{
// この200調整したほうがよさげ。
extension = 2 + (value < singularBeta - 200 && !ttCapture);
depth += depth < 15;
}
}
↓
if (value < singularBeta)
{
int doubleMargin = 262 * PvNode - 204 * !ttCapture;
int tripleMargin = 97 + 266 * PvNode - 255 * !ttCapture + 94 * ss->ttPv;
extension = 1 + (value < singularBeta - doubleMargin)
+ (value < singularBeta - tripleMargin);
depth += ((!PvNode) && (depth < 14));
}
// multipleExtensionsは、前のノードで延長したかと本ノードで延長したかを加算した値
ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2);
⇨ 削除
V8.34a1
- clean up
extern
keyword
V8.34a0
-
remove_whitespace()とis_whitespace()実装忘れていたの修正。
-
read_file_to_string()実装忘れていたの修正。
V8.34 基準
- Stockfishからnuma.h / cppをporting 作業中その1
- ThreadIdOffsetエンジンオプション廃止
- Stockfishのmisc.hのsplit()とstr_to_size_t()をporting
- StringExtension::Split()をStockfishで実装されたsplit()に差し替え。それに伴う修正。
- TranspositionTableをstructからclassに変更。
- ThreadPoolをstructからclassに変更。
## ThreadIdOffset
複数のプロセッサグループが存在する環境で、思考エンジンを複数起動した時に、各プロセッサグループに思考エンジンを割り振るための調整用。
🖋 Windows版でのみ必要となる項目です。Linuxなど他のOSですと、プロセッサグループに分かれていません。
3990XのようなWindows上で複数のプロセッサグループを持つCPUで、思考エンジンを同時起動したときに同じプロセッサグループに割り当てられてしまうのを避けるために、スレッドオフセットを指定します。
例えば、128スレッドあって、4つ思考エンジンを起動してそれぞれにThreads = 32を指定する場合、何も指定しないとどのプロセッサグループに思考エンジンが割り当てられるかは決められていません。運悪く同じプロセッサグループに割り当てられることがあります。
そこで、この時、それぞれの思考エンジンにはThreadIdOffset = 0,32,64,96をそれぞれ指定します。
💡 プロセッサグループは64論理コアごとに1つ作られます。上のケースでは、ThreadIdOffset = 0,0,64,64でも同じ意味です。
⚠ 1つのPCで複数の思考エンジンを同時に起動して対局させる場合はこれを適切に設定すべきです。並列自己対局させるようなプログラムは、このオプションに対応するか、各エンジンを起動するNUMAノードを調整できるようになっているべきだと思います。
- workflowでartifact@v4になってnameの制限が変わっている模様その7。
- actions/upload-artifact@v4使うのやめる。 ↓のようにコメントアウト
# - uses: actions/upload-artifact@v4
# with:
# name: build-wasm_${{ github.run_number }}_${{ matrix.edition }}_${{ github.sha }}
# path: ./main/build/**/**/*
-
jni/Android.mkにmemory.cpp追加してなかったの修正。
-
Androidのndkをバージョンアップ r27 ⇨ r23に戻す。(32bit用のビルドテストもしたいので..)
-
workflowでartifact@v4になってnameの制限が変わっている模様その6。
- Androidのndkをバージョンアップ r23b ⇨ r27。
-
workflowでartifact@v4になってnameの制限が変わっている模様その5。
- Ubuntu-build修正。
-
workflowでartifact@v4になってnameの制限が変わっている模様その4。
- macOS用とUbuntu-build修正。
-
memory.h/cpp追加。 ⇨ 作業済み
-
workflowでartifact@v4になってnameの制限が変わっている模様その3。
- name生成のときに
_${{ matrix.edition }}_${{ matrix.compiler }}_${{ matrix.target }}_${{ matrix.archcpu }}
を付与する。
- name生成のときに
-
workflowでartifact@v4になってnameの制限が変わっている模様その2。
- name生成のときに job IDとして github.run_id も付与する
-
workflowでartifact@v4になってnameの制限が変わっている模様。
- name生成のときに job IDとして github.job を付与する
-
Add helpers for managing aligned memory : https://github.com/official-stockfish/Stockfish/commit/00a28ae325688346e63a452b2050bd1491085359
-
TranspositionTableのデストラクタでメモリ開放するように。
-
namespace Stockfishに入れるか…。 ⇨ namespace Stockfishをすべてコメントアウトすることにした。
-
KPPT,KPP_KKPTのメモリ確保修正。
-
NNUE LEARNのメモリ確保、ビルド通ってなかったの修正。
-
large pagesを使うときのメッセージは初回のみにする。
// アライメント付きmake_shared
template <typename T, typename... ArgumentTypes>
std::shared_ptr<T> MakeAlignedSharedPtr(ArgumentTypes&&... arguments) {
// Trainerクラスのほうでゼロ初期化するのでここではゼロ初期化はされていないメモリで良い。
void* ptr_ = LargeMemory::static_alloc(sizeof(T), alignof(T));
const auto ptr = new(ptr_)
T(std::forward<ArgumentTypes>(arguments)...);
LargeMemoryDeleter<T> deleter;
//sync_cout << "trainer.alloc(" << sizeof(T) << "," << alignof(T) << ")" << sync_endl;
return std::shared_ptr<T>(ptr,deleter);
}
⇨ 削除
// 評価関数テーブルの読み込み用のメモリ
void* eval_memory = nullptr;
void eval_malloc()
{
// benchコマンドなどでOptionsを保存して復元するのでこのときEvalDirが変更されたことになって、
// 評価関数の再読込の必要があるというフラグを立てるため、この関数は2度呼び出されることがある。
// メモリ確保は一回にして、連続性のある確保にする。
aligned_large_pages_free(eval_memory);
eval_memory = aligned_large_pages_alloc(size_of_eval);
eval_assign(eval_memory);
https://github.com/yaneurao/YaneuraOu/pull/292
プロジェクトファイルに不足ファイルを追加 #292
SotckfishからAffineTransformを移植してきた際に、プロジェクトファイルに追加し忘れていたのを修正しておきました。
- Stockfishからmemory.h / cppをporting
- LargeMemoryクラス削除
- TT.clear()で、generation8もクリアするように。
- NNUEのFeatureTransformerをLargePageから確保できるようにした。(LargePageが使える環境なら少し高速化されるかも?)
- ASSERT_ALIGNED追加。
- affine_transform_sparse_input.hでcastの警告でてたの修正。
// --------------------
// Large Page確保
// --------------------
// Large Pageを確保するwrapper class。
// WindowsのLarge Pageを確保する。
// Large Pageを用いるとメモリアクセスが速くなるらしい。
// 置換表用のメモリなどはこれで確保する。
// cf. やねうら王、Large Page対応で10数%速くなった件 : http://yaneuraou.yaneu.com/2020/05/31/%e3%82%84%e3%81%ad%e3%81%86%e3%82%89%e7%8e%8b%e3%80%81large-page%e5%af%be%e5%bf%9c%e3%81%a710%e6%95%b0%e9%80%9f%e3%81%8f%e3%81%aa%e3%81%a3%e3%81%9f%e4%bb%b6/
//
// Stockfishでは、Large Pageの確保~開放のためにaligned_ttmem_alloc(),aligned_ttmem_free()という関数が実装されている。
// コードの簡単化のために、やねうら王では独自に本classからそれらを用いる。
struct LargeMemory
{
// LargePage上のメモリを確保する。Large Pageに確保できるなら、そこにする。
// aligned_ttmem_alloc()を内部的に呼び出すので、アドレスは少なくとも2MBでalignされていることは保証されるが、
// 気になる人のためにalignmentを明示的に指定できるようになっている。
// メモリ確保に失敗するか、引数のalignで指定したalignmentになっていなければ、
// エラーメッセージを出力してプログラムを終了させる。
// size : 確保するサイズ [byte]
// align : 返されるメモリが守るべきalignment
// zero_clear : trueならゼロクリアされたメモリ領域を返す。
void* alloc(size_t size, size_t align = 256 , bool zero_clear = false);
// alloc()で確保したメモリを開放する。
// このクラスのデストラクタからも自動でこの関数が呼び出されるので明示的に呼び出す必要はない(かも)
void free();
// alloc()が呼び出されてメモリが確保されている状態か?
bool alloced() const { return ptr != nullptr; }
// alloc()のstatic関数版。この関数で確保したメモリはstatic_free()で開放する。
static void* static_alloc(size_t size, size_t align = 256, bool zero_clear = false);
// static_alloc()で確保したメモリを開放する。
static void static_free(void* mem);
~LargeMemory() { free(); }
private:
// allocで確保されたメモリの先頭アドレス
// (free()で開放するときにこのアドレスを用いる)
void* ptr = nullptr;
};
// --------------------
// Large Page確保
// --------------------
namespace {
// LargeMemoryを使っているかどうかがわかるように初回だけその旨を出力する。
bool largeMemoryAllocFirstCall = true;
}
/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
/// does not guarantee the availability of aligned_alloc(). Memory allocated with
/// std_aligned_alloc() must be freed with std_aligned_free().
void* std_aligned_alloc(size_t alignment, size_t size) {
#if defined(POSIXALIGNEDALLOC)
void* mem;
return posix_memalign(&mem, alignment, size) ? nullptr : mem;
#elif defined(_WIN32)
return _mm_malloc(size, alignment);
#elif defined(__EMSCRIPTEN__)
return aligned_alloc(alignment, size);
#else
return std::aligned_alloc(alignment, size);
#endif
}
void std_aligned_free(void* ptr) {
#if defined(POSIXALIGNEDALLOC)
free(ptr);
#elif defined(_WIN32)
_mm_free(ptr);
#else
free(ptr);
#endif
}
// Windows
#if defined(_WIN32)
static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
// Windows 64bit用専用。
// Windows 32bit用ならこの機能は利用できない。
#if !defined(_WIN64)
return nullptr;
#else
// ※ やねうら王独自拡張
// LargePageはエンジンオプションにより無効化されているなら何もせずに返る。
if (!Options["LargePageEnable"])
return nullptr;
HANDLE hProcessToken { };
LUID luid { };
void* mem = nullptr;
const size_t largePageSize = GetLargePageMinimum();
// 普通、最小のLarge Pageサイズは、2MBである。
// Large Pageが使えるなら、ここでは 2097152 が返ってきているはず。
if (!largePageSize)
return nullptr;
// Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll"));
if (!hAdvapi32)
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
// Large Pageを使うには、SeLockMemory権限が必要。
// cf. http://awesomeprojectsxyz.blogspot.com/2017/11/windows-10-home-how-to-enable-lock.html
auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"));
if (!fun6)
return nullptr;
auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
if (!fun7)
return nullptr;
auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
if (!fun8)
return nullptr;
// We need SeLockMemoryPrivilege, so try to enable it for the process
if (!fun6( // OpenProcessToken()
GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
return nullptr;
if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)
nullptr, "SeLockMemoryPrivilege", &luid))
{
TOKEN_PRIVILEGES tp { };
TOKEN_PRIVILEGES prevTp { };
DWORD prevTpLen = 0;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
if (fun8( // AdjustTokenPrivileges()
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
GetLastError() == ERROR_SUCCESS)
{
// Round up size to full pages and allocate
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
mem = VirtualAlloc(
nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
// Privilege no longer needed, restore previous state
fun8( // AdjustTokenPrivileges ()
hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
}
}
CloseHandle(hProcessToken);
return mem;
#endif // _WIN64
}
void* aligned_large_pages_alloc(size_t allocSize) {
// ※ ここでは4KB単位でalignされたメモリが返ることは保証されているので
// 引数でalignを指定できる必要はない。(それを超えた大きなalignを行いたいケースがない)
//static bool firstCall = true;
// try to allocate large pages
void* ptr = aligned_large_pages_alloc_windows(allocSize);
// Suppress info strings on the first call. The first call occurs before 'uci'
// is received and in that case this output confuses some GUIs.
// uciが送られてくる前に"info string"で余計な文字を出力するとGUI側が誤動作する可能性があるので
// 初回は出力を抑制するコードが入っているが、やねうら王ではisreadyでメモリ初期化を行うので
// これは気にしなくて良い。
// 逆に、評価関数用のメモリもこれで確保するので、何度もこのメッセージが表示されると
// 煩わしいので、このメッセージは初回のみの出力と変更する。
// if (!firstCall)
if (largeMemoryAllocFirstCall)
{
if (ptr)
sync_cout << "info string Hash table allocation: Windows Large Pages used." << sync_endl;
else
sync_cout << "info string Hash table allocation: Windows Large Pages not used." << sync_endl;
largeMemoryAllocFirstCall = false;
}
// fall back to regular, page aligned, allocation if necessary
// 4KB単位であることは保証されているはず..
if (!ptr)
ptr = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
// VirtualAlloc()はpage size(4KB)でalignされていること自体は保証されているはず。
//cout << (u64)mem << "," << allocSize << endl;
return ptr;
}
#else
// LargePage非対応の環境であれば、std::aligned_alloc()を用いて確保しておく。
// 最低でも4KBでalignされたメモリが返るので、引数でalignを指定できるようにする必要はない。
void* aligned_large_pages_alloc(size_t allocSize) {
#if defined(__linux__)
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size
#else
constexpr size_t alignment = 4096; // assumed small page size
#endif
// Round up to multiples of alignment
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
void* mem = std_aligned_alloc(alignment, size);
#if defined(MADV_HUGEPAGE)
madvise(mem, size, MADV_HUGEPAGE);
#endif
return mem;
}
#endif
/// aligned_large_pages_free() will free the previously allocated ttmem
#if defined(_WIN32)
void aligned_large_pages_free(void* mem) {
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
{
DWORD err = GetLastError();
std::cerr << "Failed to free large page memory. Error code: 0x"
<< std::hex << err
<< std::dec << std::endl;
exit(EXIT_FAILURE);
}
}
#else
void aligned_large_pages_free(void* mem) {
std_aligned_free(mem);
}
#endif
// --------------------
// LargeMemory class
// --------------------
// メモリを確保する。Large Pageに確保できるなら、そこにする。
// aligned_ttmem_alloc()を内部的に呼び出すので、アドレスは少なくとも2MBでalignされていることは保証されるが、
// 気になる人のためにalignmentを明示的に指定できるようになっている。
// メモリ確保に失敗するか、引数のalignで指定したalignmentになっていなければ、
// エラーメッセージを出力してプログラムを終了させる。
void* LargeMemory::alloc(size_t size, size_t align , bool zero_clear)
{
free();
return static_alloc(size, align, zero_clear);
}
// alloc()で確保したメモリを開放する。
// このクラスのデストラクタからも自動でこの関数が呼び出されるので明示的に呼び出す必要はない(かも)
void LargeMemory::free()
{
static_free(ptr);
ptr = nullptr;
}
// alloc()のstatic関数版。memには、static_free()に渡すべきポインタが得られる。
void* LargeMemory::static_alloc(size_t size, size_t align, bool zero_clear)
{
void* mem = aligned_large_pages_alloc(size);
auto error_exit = [&](std::string mes) {
sync_cout << "info string Error! : " << mes << " in LargeMemory::alloc(" << size << "," << align << ")" << sync_endl;
Tools::exit();
};
// メモリが正常に確保されていることを保証する
if (mem == nullptr)
error_exit("can't alloc enough memory.");
// ptrがalignmentされていることを保証する
if ((reinterpret_cast<size_t>(mem) % align) != 0)
error_exit("can't alloc aligned memory.");
// ゼロクリアが必要なのか?
if (zero_clear)
{
// 確保したのが256MB以上なら並列化してゼロクリアする。
if (size < 256 * 1024 * 1024)
// そんなに大きな領域ではないから、普通にmemset()でやっとく。
std::memset(mem, 0, size);
else
// 並列版ゼロクリア
Tools::memclear(nullptr, mem, size);
}
return mem;
}
// static_alloc()で確保したメモリを開放する。
void LargeMemory::static_free(void* mem)
{
aligned_large_pages_free(mem);
}
https://github.com/yaneurao/YaneuraOu/pull/291
ifdefの条件を修正した #291
変更後も変更前も (現状は) 常に偽であることは変わりはないですが、Stockfishのようにvnni256に対応したときのために修正しておきました。
ifdefの条件を修正した
NEONでのエラーを修正
NEONの方のコードも修正しました。
- やねうら王 V8.33
https://github.com/yaneurao/YaneuraOu/pull/290
* evallearnでのビルドエラーを修正
* k接頭辞をつけ忘れていたのを修正
* AVXVNNIでビルドエラーが出るのを修正
-
upload-artifact@v4.1.7⇨upload-artifact@v4に変更。
-
upload-artifact@v2⇨upload-artifact@v4.1.7に変更。
-
numa.h 追加。 ⇨ 時間かかりそう。あとまわし。
・AVX512のバイナリ、実機で動作しない件、調査する。 ⇨ 完了。
https://github.com/yaneurao/YaneuraOu/pull/289
Stockfish17のAffineTransformとAffineTransformSparseInputを移植した #289
概要
Stockfish17のAffineTransformとAffineTransformSpraseInputを移植してきました。
探索depth固定でのbenchコマンドのノード数も変化しておらず(AVX2やAVX-512では)、正しく動作しているようです。
SSE4.2以下や、NEONについてはこちらでは検証できておりません。
ベンチマーク
R9-7945HXでAVX-512有効の場合、標準NNUEでは移植してきたAffineTransformを使うことで9.2%、さらにAffineTransformSparseInputを使うことで10.7%ほどNPSが向上することが確認できました。
ただし、halfkp_512x2-8-64やhalfkp_512x2-8-96ではNPSの低下が確認されています。
https://github.com/yaneurao/YaneuraOu/pull/288
ClippedReluのAVX-512対応を実装 #288
概要
ClippedReLU::PropagateでAVX-512が有効な場合の実装を行いました。
考慮点
halfkp_512x2-8-96のようなアーキテクチャの場合に、AVX-512で処理すると端数が生じ、その分速度が低下したためkInputDimensionsの値に合わせてフォールバックするようにしました。
ベンチマーク
こちらの環境 (R9-7945HX) では (変更前でも変更後でも) AVX-512を有効にするとNPSが若干低下するため、この変更による速度向上については計測できていません。
https://github.com/yaneurao/YaneuraOu/pull/287
* AVX-512でクラッシュする問題を修正
* AVX-512で入力特徴量を変換する際にすべてのトリガーに対して積算できていないのを修正
https://github.com/yaneurao/YaneuraOu/pull/285
ThreadIdOffsetの最大値が複数NUMAノード持つCPUの時に正しく指定されていないのを修正した。 #285
std::thread::hardware_concurrency()が複数NUMAノード持つCPUでは期待している値を返さないので、修正しました。
⇨ Stockfishのnuma.hをportingしてくることにして、これはreject。
https://github.com/yaneurao/YaneuraOu/pull/284
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 4.1.7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v2...v4.1.7)
---
updated-dependencies:
- dependency-name: actions/download-artifact
dependency-type: direct:production
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
https://github.com/yaneurao/YaneuraOu/pull/283
* 方向利きのUnitTestで`all_ok &=`の代わりに`all_ok =`になっていたのを修正
* movegen.cppにも`all_ok &=`の代わりに`all_ok =`と書いているところがあったため修正した
- GitHubのReleasesのところ、V7.xx ⇨ V8.20の更新履歴書くの忘れていた。
- 探索部、Stockfishの最新版にキャッチアップ
- 定跡表示の読み筋の末端がおかしいことがあったのを修正
- ペタショック定跡コマンド追加
- 32bit環境用のコード生成、色々修正
- clang・gccでLEARN版のビルド時に警告が出ていたの修正
- 探索時に千日手絡みになった時の処理、色々改良
- タイプミスの修正 #279
engine1 = YaneuraOu_NNUE_halfkpvm_256x2_32_32-V832DEV_AVX2.exe , eval = tanuki-dr2
engine2 = YaneuraOu_NNUE_halfKP1024X2_8_32-V830DEV_AVX2.exe , eval = Li
T2,b1000,1540 - 119 - 1341(53.45% R24.04[13.37,34.71]) winrate black , white = 53.25% , 46.75%
T2,b2000,1495 - 117 - 1388(51.86% R12.9[2.25,23.55]) winrate black , white = 51.2% , 48.8%
T2,b4000,1428 - 141 - 1431(49.95% R-0.36[-11.05,10.32]) winrate black , white = 52.64% , 47.36%
engine1 = YaneuraOu_NNUE_halfkpe9_256x2_32_32-V832DEV_AVX2.exe , eval = BBhalfkpe9
engine2 = YaneuraOu_NNUE_halfKP1024X2_8_32-V830DEV_AVX2.exe , eval = Li
T2,b1000,1248 - 82 - 1670(42.77% R-50.6[-61.29,-39.91]) winrate black , white = 51.75% , 48.25%
T2,b2000,1191 - 96 - 1713(41.01% R-63.14[-73.92,-52.36]) winrate black , white = 51.55% , 48.45%
T2,b4000,1175 - 120 - 1705(40.8% R-64.67[-75.51,-53.84]) winrate black , white = 51.77% , 48.23%
engine1 = YaneuraOu_NNUE_halfkpe9_256x2_32_32-V832DEV_AVX2.exe , eval = BBhalfkpe9
engine2 = YaneuraOu_NNUE_halfkpe9_256x2_32_32-V832DEV_AVX2.exe , eval = tttak_halfkpe9_20240313
T2,b1000,1461 - 81 - 1458(50.05% R0.36[-10.22,10.93]) winrate black , white = 51.52% , 48.48%
T2,b2000,1475 - 125 - 1400(51.3% R9.07[-1.59,19.73]) winrate black , white = 50.3% , 49.7%
T2,b4000,1512 - 156 - 1332(53.16% R22.02[11.28,32.75]) winrate black , white = 52.22% , 47.78%
⇑の見方。engine1とengine2との対局。T2 = 2スレ , b1000 = 1手1秒 , 1540 - 119 - 1341 は engine1から見て1540勝、1341敗、119引分の意味。
halfkpvm、Liより強いかと思ったら持ち時間を増やしていくと逆転しそうであった。(でもこれ以上の長い持ち時間で計測できない)
BB halfKPE9、Liより5,60弱そうだけど、これは開始局面の問題もありそう。BBはBBの定跡の先端局面からだと強いらしいので..
BB halfKPE9とtttakさんのhalfKPE9とでは、持ち時間を増やすと前者が圧勝しそう。(でもこれ以上の長い持ち時間で計測できない)
おわり。😵💫
V8.30 → V8.32更新箇所 ・halfkpe9用で詰みの局面で思考させた時に落ちてたの修正。 ・32bit OS用のhalfkpe9で、利きの計算が間違っていたの修正。 ・configコマンドで評価関数のアーキテクチャを出力するようにした。 ・間違った評価関数ファイルを読み込ませた時に、評価関数ファイルのmismatchである旨を出力するようにした。 ・Ubuntu24.04のclang-18でビルドに失敗していたのを修正。 ・配布用の実行ファイルにhalfkpvm-256x2-32-32の実行ファイル追加。たぬきさんが過去に配布してた。
- 32bit OS用のNNUE_halfkpe9用のコンパイルでwarningが出るの修正。
long_effect.cpp:174:34: warning: implicit conversion from 'long long' to 'u32'
(aka 'unsigned int') changes value from 2134471622175 to 4167843359 [-Wconstant-conversion]
174 | return (Directions)PEXT64(t, 0b11111000011111000011011000011111000011111);
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- halfkpe9、詰みの局面で落ちるの修正。
- 通常のNNUEでも、Considerationモードの時に、MOVE_RESIGNのような非合法手でdo_move()する実装になっていたので、その点を修正した。
evaldir BBhalfkpe9
evaldir hao
isready
position startpos moves 7g7f 8c8d 2g2f 8d8e 8h7g 3c3d 7i6h 4c4d 4g4f 4a3b 3i3h 3a4b 6i7h 4b4c 3h4g 7a6b 4g5f 9c9d 5i6i 5c5d 2f2e 2b3c 4i5i 7c7d 6i7i 6b5c 2h4h 6a6b 3g3f 8a7c 1g1f 5a6a 9g9f 1c1d 2i3g 5c6d 4f4e 4d4e 5f4e 3c7g+ 8i7g P*4g 4h4g B*3h 4g4f 3h2g+ 5i4h 6d5e 4f4g 8e8f 8g8f 8b8f 5g5f P*4f 4g5g 5e6d P*4d 4c5b P*8g 8f8b 5f5e 2a3c 4e3d 2g2f B*2a 2f4d 2a3b+ 4d3d G*3e 3d4c 3b4c 5b4c 5e5d P*5e B*2b 4c5d 2b3c+ 7c6e 5g5i 6e7g+ 6h7g N*5f 4h5h S*4h N*6f 5d6e N*8f 6b7c 3c4d 4h3g+ 5i4i 4f4g+ 5h4g P*4h 4g5f 4h4i+ 5f6e 6d6e 7i8i N*8e S*6b 8b6b 4d6b 6a6b R*8b 6b7a 8b8e+ R*5i N*7i B*4g P*5f 4g5f+ P*5g P*8h 8i8h 5i5g+ N*6h 5f4g P*5f S*6d 3e4d P*8d 8e6e 6d6e 7f7e 6e6f S*8b 7a8b 6g6f N*8e 7i6g R*6i S*7i 8e7g+ 8h7g 5g6g 7g6g 4g5f 6g7g N*8e 7g8h S*7g 7h7g G*8i 8h9h S*9g
go btime 7000 wtime 16000 binc 1000 winc 1000
// YaneuraOuNNUE_V831_1024d.exe
- 一度打ち歩詰め局面に遭遇したら、以降gameoverになるまで不成を生成するように。
engine1 = YaneuraOuNNUE_V831_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V831_1024d.exe , eval = tanuki20240131 T2,b1000,1252 - 136 - 1122(52.74% R19.04[7.3,30.79]) winrate black , white = 51.85% , 48.15% T2,b2000,617 - 83 - 510(54.75% R33.09[15.99,50.18]) winrate black , white = 52.71% , 47.29% T2,b4000,274 - 53 - 223(55.13% R35.78[10.03,61.53]) winrate black , white = 51.51% , 48.49%
// YaneuraOuNNUE_V831_1024c.exe
- ロジックを最適化した。
engine1 = YaneuraOuNNUE_V831_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V831_1024c.exe , eval = tanuki20240131 T2,b1000,1497 - 159 - 1344(52.69% R18.73[7.99,29.46]) winrate black , white = 52.02% , 47.98% T2,b2000,1405 - 244 - 1141(55.18% R36.16[24.77,47.54]) winrate black , white = 51.89% , 48.11%
⇨ まだ悪いなー。指し手を進めた時に詰められない可能性とかあるのか?
やねうらお — 今日 12:10
改造したほう、R20ぐらい悪かった。
不成の指し手を生成するだけなら -R10 ぐらいで済むはずなので、R20も悪いのはバグってるのか何かなのだけど、短いコードなのでバグってるというのは考えにくく、探索上の原因だと思う。
例えば、こういうシナリオ。
打ち歩詰め発見 ⇨ 不成を生成開始 ⇨ 不成で詰むことを確認 ⇨ 不成の指し手がhistory statsで上位に来る ⇨ その読み筋であるbestmoveを返す ⇨ 次の局面がGUIから送られてくる ⇨ 新たにgoする ⇨ 不成は生成していない ⇨ 打ち歩詰めの変化を発見できず ⇨ history statsの上位は不成で詰む変化だが、不成は生成していないので成るほうの指し手がorderingの1位にくる ⇨ その指し手では詰まない ⇨ 間違った指し手がordering上位に来るので探索を阻害 ⇨ あるいは不成で詰んでいたから他の指し手を読んでおらず、それ以外の指し手のhistory statsがぐちゃぐちゃ ⇨ 探索の役に立たない ⇨ おわり🥲
そんなわけで、一度でも打ち歩詰めの局面に遭遇した場合、gameoverまで不成を生成するモードに切り替えるのが正しいのではないかという気がしてきた。
やねうらお — 今日 14:19
なんとなく原因わかった。
打ち歩詰めを検出するコストが余分にかかることが問題の原因っぽい。
通常の1手詰めルーチンでは、打ち歩で詰むかどうかは判定していない。ところが今回の改造では、この判定が追加で必要であり、1手詰めルーチンは新規ノードで必ず呼び出されるため、このような追加のコストは馬鹿にならない。
それゆえ、不成を無条件で生成するよりさらにコストを要するようだ。
この判定コストがもっと小さいなら、いいアイデアなのかも知れないのだが…。
Long Effect Library(盤面の利きを計算する)を用いている時なら、打ち歩で詰むかの判定コストは小さいのでこの改造が有用な可能性はなくはないのだが…。
いずれにせよ、今回は駄目っぽいということで..🥲
// YaneuraOuNNUE_V831_1024b.exe
- 探索中に打ち歩詰め局面に遭遇したら、そこ以降、不成の指し手を生成するようにした。
engine1 = YaneuraOuNNUE_V831_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V831_1024b.exe , eval = tanuki20240131 T2,b1000,1503 - 146 - 1351(52.66% R18.52[7.81,29.23]) winrate black , white = 53.01% , 46.99% T2,b1000,1506 - 152 - 1342(52.88% R20.03[9.3,30.75]) winrate black , white = 53.05% , 46.95% T2,b2000,1542 - 224 - 1234(55.55% R38.71[27.8,49.62]) winrate black , white = 51.84% , 48.16% T2,b4000,732 - 125 - 633(53.63% R25.24[9.74,40.75]) winrate black , white = 52.6% , 47.4%
// 現バージョン : YaneuraOuNNUE_V831_1024a.exe
- configコマンドで評価関数アーキテクチャが表示されるようにした。
- makefile、NNUEの任意アーキテクチャのビルド対応。
-
NNUE architecture、ビルド時に生成されたheader fileの読み込みに対応
-
NNUEのarchitecture headerを動的に生成するスクリプト作った。
-
k_p_256x2-32-32.hのファイル名 kp_256x2-32-32.h にrename。(入力特徴量名を'_'の左側に持ってきたいため。)
-
halfkp_vm_256x2-32-32.hもhalfkpvm_256x2-32-32.h とrename。
-
NNUEのarchitectureのheaderでifdefで囲ってないものがあったのを修正。
-
GitHub Actionsのmac用、OSをmacOS-11⇨13に変更。
-
clang-18 on Ubuntu 24.04でlinkerのエラーになっていたの修正。
- makebook peta_shockコマンド、一時ファイルの書き出すpathが一貫していなかったの修正。
-
Makefile、macOS用の説明が間違っていたの修正。
-
NNUEのnn.binの読み込み失敗の時に、FileNotFoundなのかFileReadErrorなのかFileMismatchなのかがわかるようにした。
- Tools::ResultCodeにいくつか追加した。
- clang, gccで32bit環境用にSSE4.2用、AVX2用がビルドできない件、修正。
- やねうら王のインストール手順の古い説明(あとで復活させるか考える)
1) 駒得型 (ファイル名 : YaneuraOu_MaterialLv1_XXX.exe)
→ これは駒得だけを評価する思考エンジンなので、評価関数のファイルは不要です。
💡 MaterialLv2~は、以下の連載記事で作ったもので、単なる駒得の評価関数よりは強くなりますが、実行ファイルはたくさんあるので毎回は配布はしていません。
【連載】評価関数を作ってみよう!その1 : http://yaneuraou.yaneu.com/2020/11/17/make-evaluate-function/
5) tanuki-詰将棋エンジン(tanuki_MATE_XXX.exe)
💡 これは、詰将棋を解くの専用の思考エンジンなので評価関数のファイルは不要です。
- XXXのところには、avx2やsse42のようにCPUの種別が入ります。
- ファイル名の途中にlearnとついているものは、評価関数の学習のために用いるもので、通常対局用ではありません。
評価関数の種類に関して詳しくは以下の説明もご覧ください。
- [評価関数の種類 - やねうら王のビルド手順](やねうら王のビルド手順#評価関数の種類)
やねうらお — 今日 19:21
■ Stockfishが地雷の件
Stockfishの2024年3月~6月5日現在に至るまでのcommit、やねうら王にマージして強くなる改良が一つもなさそう。これはマジで吐きそう。
せめて同じ強さが維持できるなら、マージするのだが、マージしてR3とかR5ずつ損していった結果、最終的にR25ぐらい弱いのはさすがにどうにもならない。
rollbackして半年ぐらい寝かせておくか…。😵💫
やねうらお — 今日 22:10
■ AVX512関係のアレ
やねうら王でAVX512用にビルドしたやつ、時々動かないとか何とか言う問題、もしかして⇓これと関係あるのかな。64 bytesでalignされてないメモリ使ってるところがどこかにある、的な..🤔
https://github.com/official-stockfish/Stockfish/commit/00a28ae325688346e63a452b2050bd1491085359
GitHub
Add helpers for managing aligned memory · official-stockfish/Stockf...
Previously, we had two type aliases, LargePagePtr and AlignedPtr, which
required manually initializing the aligned memory for the pointer.
The new helpers:
- make_unique_aligned
- make_unique_lar...
やねうらお — 今日 22:17
やねうら王のメモリのアロケーション、これで書き換えるの、わりと大変なのだ..。
ついでに、StockfishではNUMAのアロケーションもだいぶアルゴリズムが変わったみたいなのだ。
マージしても1㍉も強くならないような改造が90%なのだ。あとの9%はマージすると弱くなる改造なのだ。残りの1%がマージすると強くなる改造なのだ。終わり。😢
やねうらお — 今日 19:25
欲を言うならStockfishを忠実にマージしたやねうら王と、強くなるcommitだけマージしたやねうら王と、2つ用意したいところではあるなー。長い時間だと前者の方が強いということはありえなくはないしなー。
でもそんなのソースコードを管理するの、めちゃめちゃ大変なんだよなー。
いまですら、GitHub版と開発版(支援者向け)と手元の開発版と3つあるのに..。
// V8.30git
- バージョン V8.30git 実行ファイルあとでGitHubのReleasesのところに生やす。
・バージョンナンバーをV8.30gitに変更。
engine1 = YaneuraOuNNUE1024_2024.05.28-cl4_avx2.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830y.exe , eval = tanuki20240131
T2,b1000,1061 - 135 - 1804(37.03% R-92.21[-103.26,-81.16]) winrate black , white = 51.34% , 48.66%
T2,b2000,1083 - 178 - 1739(38.38% R-82.27[-93.33,-71.21]) winrate black , white = 53.58% , 46.42%
engine1 = YaneuraOuNNUE1024_V820DEV-cl4_avx2.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830y.exe , eval = tanuki20240131
T2,b1000,1357 - 155 - 1488(47.7% R-16.01[-26.73,-5.29]) winrate black , white = 51.95% , 48.05%
T2,b2000,1328 - 231 - 1441(47.96% R-14.19[-25.05,-3.32]) winrate black , white = 52.69% , 47.31%
engine1 = YaneuraOuNNUE1024X2_8_32-V830GitHub_AVX2.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830y.exe , eval = tanuki20240131
T2,b1000,978 - 136 - 1886(34.15% R-114.08[-125.34,-102.82]) winrate black , white = 52.16% , 47.84%
T2,b2000,1005 - 203 - 1792(35.93% R-100.47[-111.73,-89.21]) winrate black , white = 53.45% , 46.55%
engine1 = YaneuraOuNNUE1024X2_8_32-V830DEV_AVX2.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830y.exe , eval = tanuki20240131
T2,b1000,1312 - 145 - 1543(45.95% R-28.17[-38.9,-17.44]) winrate black , white = 54.05% , 45.95%
T2,b2000,1266 - 213 - 1521(45.43% R-31.88[-42.75,-21.01]) winrate black , white = 50.74% , 49.26%
・Stockfishの6月1日~6月6日のcommit対応終わり。
// 830y
よさげなcommitだけ反映。
- 830q
- 830r
- 830s
- これいいかどうかわからん
- 830t
-
これは適用済み
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830y.exe , eval = tanuki20240131 T2,b1000,1509 - 170 - 1321(53.32% R23.11[12.35,33.88]) winrate black , white = 53.96% , 46.04% T2,b2000,1400 - 211 - 1389(50.2% R1.37[-9.45,12.19]) winrate black , white = 53.28% , 46.72% T2,b4000,1402 - 291 - 1307(51.75% R12.19[1.2,23.17]) winrate black , white = 53.3% , 46.7% ⇨ 長い持ち時間では悪くない可能性があるっぽい。
T2,b8000,668 - 194 - 738(47.51% R-17.31[-32.57,-2.06]) winrate black , white = 53.63% , 46.37% T2,b8000,701 - 189 - 700(50.04% R0.25[-15.02,15.51]) winrate black , white = 53.18% , 46.82% T2,b8000,656 - 180 - 634(50.85% R5.93[-9.98,21.83]) winrate black , white = 53.33% , 46.67% T2,b8000,321 - 94 - 295(52.11% R14.67[-8.36,37.7]) winrate black , white = 50.49% , 49.51% ⇨ 2346-2367 -R1.5 計測不可な差なので、これは良し。
GitHub版と強さ比較する。
-
// 830x
よさげなcommitだけ反映
- V830g
- 機能変更なし
- V830p
- +R10ぐらい強くなるのか?
- V830h
-
誤差のはず
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830x.exe , eval = tanuki20240131 T2,b1000,1398 - 172 - 1430(49.43% R-3.93[-14.68,6.81]) winrate black , white = 51.98% , 48.02% T2,b2000,1346 - 211 - 1443(48.26% R-12.09[-22.91,-1.26]) winrate black , white = 52.85% , 47.15% T2,b4000,1350 - 306 - 1344(50.11% R0.77[-10.23,11.78]) winrate black , white = 53.45% , 46.55%
⇨ まあ、良し。
-
// 830w
よさげなcommitだけ反映。
- V830c
- V830d
- Remove rootDelta branch : https://github.com/official-stockfish/Stockfish/commit/8e1f273c7d10e2b49c07cdc16b09a3d4574acf4c
- ⇑こっちだけ
- V830e
-
reductionまわり、よくわからんからStockfishの最新コードから丸ごともってきて差し替えた。 ⇨ このcommit、なんかまずそう?
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830w.exe , eval = tanuki20240131 T2,b1000,1430 - 163 - 1407(50.41% R2.82[-7.91,13.54]) winrate black , white = 50.26% , 49.74% T2,b2000,1419 - 203 - 1378(50.73% R5.09[-5.71,15.9]) winrate black , white = 51.56% , 48.44% T2,b4000,1370 - 274 - 1356(50.26% R1.78[-9.16,12.73]) winrate black , white = 52.75% , 47.25%
-
// 830v
あかん。ぜんぶrollbackして、弱くならなかったcommitだけを適用する。
- 830aから。
影響なさそうなやつだけまずは反映。これでR±0のはず。
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830v.exe , eval = tanuki20240131
T2,b1000,1406 - 170 - 1424(49.68% R-2.21[-12.95,8.53]) winrate black , white = 53.0% , 47.0%
T2,b2000,1357 - 223 - 1420(48.87% R-7.88[-18.73,2.96]) winrate black , white = 52.36% , 47.64%
// 830u
-
Tweak first picked move (ttMove) reduction rule : https://github.com/official-stockfish/Stockfish/commit/de1ae4949daf2c6d36c50e51c132cee808e2ade0
engine1 = YaneuraOuNNUE1024_830t.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830u.exe , eval = tanuki20240131 T2,b1000,1471 - 114 - 1415(50.97% R6.74[-3.9,17.38]) winrate black , white = 51.52% , 48.48% ⇨ 悪そう..
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830u.exe , eval = tanuki20240131 T2,b1000,1529 - 136 - 1335(53.39% R23.57[12.87,34.27]) winrate black , white = 51.68% , 48.32% T2,b2000,969 - 129 - 872(52.63% R18.32[4.99,31.66]) winrate black , white = 52.91% , 47.09%
// 830t
-
Avoid changing bestvalue : https://github.com/official-stockfish/Stockfish/commit/b0870cf528ef90e8873719a36a448dafd73e3aee
engine1 = YaneuraOuNNUE1024_830s.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830t.exe , eval = tanuki20240131 T2,b1000,1448 - 124 - 1428(50.35% R2.42[-8.24,13.07]) winrate black , white = 52.09% , 47.91%
// 830s
-
Simplify histories movepick formula : https://github.com/official-stockfish/Stockfish/commit/ec1cda1d819f534c8d0bfc4624836157bc548eb6
engine1 = YaneuraOuNNUE1024_830r.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830s.exe , eval = tanuki20240131 T2,b1000,1461 - 111 - 1428(50.57% R3.97[-6.66,14.6]) winrate black , white = 50.74% , 49.26%
// 830r
-
Simplify statScore divisor into a constant : https://github.com/official-stockfish/Stockfish/commit/c17d73c554054db8cdc6eb39d667c1dca47d3818
engine1 = YaneuraOuNNUE1024_830q.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830r.exe , eval = tanuki20240131 T2,b1000,1420 - 133 - 1447(49.53% R-3.27[-13.94,7.4]) winrate black , white = 51.41% , 48.59%
// 830q
-
Simplify continuation histories : https://github.com/official-stockfish/Stockfish/commit/3d6756769cd159edf1d7eaec074c880551590c32
engine1 = YaneuraOuNNUE1024_830p.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830q.exe , eval = tanuki20240131 T2,b1000,1428 - 118 - 1454(49.55% R-3.13[-13.78,7.51]) winrate black , white = 53.16% , 46.84%
// 830p ☆
-
Adjust lowest depth constants to the natural place : https://github.com/official-stockfish/Stockfish/commit/397f47a7a1b7abe490d7bcb7a526d01555aed2be
-
Simplify recapture extension : https://github.com/official-stockfish/Stockfish/commit/924a843594743297f47edf7b0931ede8dcbb6dd8
engine1 = YaneuraOuNNUE1024_830o.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830p.exe , eval = tanuki20240131 T2,b1000,1458 - 134 - 1408(50.87% R6.06[-4.61,16.74]) winrate black , white = 50.52% , 49.48% T2,b2000,1406 - 182 - 1412(49.89% R-0.74[-11.5,10.02]) winrate black , white = 53.55% , 46.45% T2,b4000,1038 - 147 - 1105(48.44% R-10.87[-23.21,1.48]) winrate black , white = 52.64% , 47.36%
// 830o
-
Add extra bonus for high-depth condition : https://github.com/official-stockfish/Stockfish/commit/fa114266fa7ea996c6d2ef12c625547b1aefddc1
engine1 = YaneuraOuNNUE1024_830n.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830o.exe , eval = tanuki20240131 T2,b1000,1424 - 121 - 1455(49.46% R-3.74[-14.39,6.91]) winrate black , white = 52.17% , 47.83% T2,b2000,1414 - 164 - 1422(49.86% R-0.98[-11.71,9.75]) winrate black , white = 52.26% , 47.74% T2,b4000,1255 - 205 - 1200(51.12% R7.78[-3.75,19.32]) winrate black , white = 53.08% , 46.92% ⇨ 悪いっぽい..??
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830o.exe , eval = tanuki20240131 T2,b1000,1515 - 144 - 1341(53.05% R21.19[10.48,31.91]) winrate black , white = 52.49% , 47.51%
// 830n
-
V830lからV830iだけrollback
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830n.exe , eval = tanuki20240131 T2,b1000,1516 - 145 - 1339(53.1% R21.57[10.85,32.28]) winrate black , white = 51.21% , 48.79% T2,b2000,1456 - 195 - 1349(51.91% R13.26[2.46,24.06]) winrate black , white = 52.69% , 47.31% T2,b4000,1058 - 195 - 977(51.99% R13.84[1.16,26.51]) winrate black , white = 52.38% , 47.62%
// 830m
-
V830lからV830kだけrollback
PARAM_NULL_MOVE_DYNAMIC_GAMMA = 132 ⇦ 177 に戻す
Depth R = std::min(int(eval - beta) / PARAM_NULL_MOVE_DYNAMIC_GAMMA, 6) + depth / 3 + 5; ⇓戻す Depth R = std::min(int(eval - beta) / PARAM_NULL_MOVE_DYNAMIC_GAMMA, 6) + depth / 3 + 4;
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830m.exe , eval = tanuki20240131 T2,b1000,1473 - 157 - 1370(51.81% R12.59[1.87,23.32]) winrate black , white = 54.03% , 45.97% T2,b2000,1507 - 177 - 1316(53.38% R23.54[12.76,34.32]) winrate black , white = 52.6% , 47.4% T2,b4000,1023 - 169 - 868(54.1% R28.54[15.36,41.73]) winrate black , white = 54.79% , 45.21%
⇨ V830k、必要だったみたい。
// V830l
-
opponentWorsening導入。
-
Simplifying improving and worsening deduction formulas : https://github.com/official-stockfish/Stockfish/commit/dcb02337844d71e56df57b9a8ba17646f953711c
engine1 = YaneuraOuNNUE1024_830k.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830l.exe , eval = tanuki20240131 T2,b1000,1467 - 132 - 1401(51.15% R8.0[-2.68,18.67]) winrate black , white = 51.64% , 48.36% T2,b2000,1363 - 185 - 1452(48.42% R-10.99[-21.76,-0.21]) winrate black , white = 52.79% , 47.21% T2,b4000,1223 - 221 - 1216(50.14% R1.0[-10.57,12.57]) winrate black , white = 52.32% , 47.68% ⇨ そんな悪くなさそう
// V830k
- Tweak NMP Formula : https://github.com/official-stockfish/Stockfish/commit/285f1d2a663fb111f7124272403923eab4251982
- Improve comment : https://github.com/official-stockfish/Stockfish/commit/e0227a627288c786fdd3b12452303ff4eabba5b0
PARAM_NULL_MOVE_DYNAMIC_GAMMA = 132 ⇨ 177 に変更
engine1 = YaneuraOuNNUE1024_830j.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830k.exe , eval = tanuki20240131
T2,b1000,1479 - 131 - 1390(51.55% R10.78[0.11,21.45]) winrate black , white = 50.82% , 49.18%
T2,b2000,1354 - 182 - 1464(48.05% R-13.57[-24.34,-2.8]) winrate black , white = 52.27% , 47.73%
T2,b4000,1074 - 204 - 1072(50.05% R0.32[-12.01,12.66]) winrate black , white = 51.26% , 48.74%
⇨ 悪くなかった。
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131
engine2 = YaneuraOuNNUE1024_830k.exe , eval = tanuki20240131
T2,b1000,1452 - 158 - 1390(51.09% R7.58[-3.14,18.3]) winrate black , white = 51.94% , 48.06%
T2,b2000,1477 - 191 - 1332(52.58% R17.95[7.16,28.75]) winrate black , white = 52.69% , 47.31%
T2,b4000,1362 - 255 - 1283(51.49% R10.38[-0.73,21.5]) winrate black , white = 53.42% , 46.58%
⇨ aに対しては強くなってなさそうなので、まずそうなのをrollbackする。
// V830j
-
singular extensionにdoubleMargin, tripleMarginを導入。
-
Simplify Away Quadruple Extensions : https://github.com/official-stockfish/Stockfish/commit/4edd1a389e4146a610098a841841f37f58980213
-
Revert "Simplify Away Quadruple Extensions" : https://github.com/official-stockfish/Stockfish/commit/2d3258162387bf38551962bf2c9dd1d47e72b4dd
engine1 = YaneuraOuNNUE1024_830i.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830j.exe , eval = tanuki20240131 T2,b1000,1456 - 142 - 1402(50.94% R6.57[-4.12,17.26]) winrate black , white = 50.87% , 49.13% T2,b2000,1446 - 184 - 1370(51.35% R9.38[-1.39,20.15]) winrate black , white = 52.95% , 47.05% T2,b2000,1414 - 193 - 1393(50.37% R2.6[-8.19,13.38]) winrate black , white = 52.51% , 47.49%
⇨ 誤差っぽいが?悪くなってるので採用せんとこ。
// V830i
-
Simplify the recently introduced ply-based cmh bonus factor. : https://github.com/official-stockfish/Stockfish/commit/81e21a69f02164fd988d5636a47c8790a1174b81
engine1 = YaneuraOuNNUE1024_830h.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830i.exe , eval = tanuki20240131 T2,b1000,1446 - 168 - 1386(51.06% R7.36[-3.38,18.1]) winrate black , white = 52.75% , 47.25% T2,b2000,1436 - 213 - 1351(51.52% R10.6[-0.23,21.43]) winrate black , white = 51.67% , 48.33% ⇨ 悪い..bonusまわり、そこだけいじると悪くなるんだよなー。
// V830h
-
Remove cutoffCnt margin adjustment in razoring : https://github.com/official-stockfish/Stockfish/commit/c86ec8ec2916924065138770e0201c2cfe6d3e72
engine1 = YaneuraOuNNUE1024_830g.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830h.exe , eval = tanuki20240131 T2,b1000,1430 - 164 - 1406(50.42% R2.94[-7.79,13.67]) winrate black , white = 53.77% , 46.23%
// V830g
-
Improve comments about DEPTH constants : https://github.com/official-stockfish/Stockfish/commit/ed79745bb9e7207b604c62758ea45dd5c597ed8d
No functional change
engine1 = YaneuraOuNNUE_V830_1024f.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_830g.exe , eval = tanuki20240131 T2,b1000,1414 - 169 - 1417(49.95% R-0.37[-11.11,10.37]) winrate black , white = 51.85% , 48.15%
// V830f
-
Allow tt cutoffs for shallower depths in certain conditions : https://github.com/official-stockfish/Stockfish/commit/b280d2f06553e8c8d98379fe547f3b995cc56d59
engine1 = YaneuraOuNNUE_V830_1024e.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V830_1024f.exe , eval = tanuki20240131 T2,b1000,1460 - 156 - 1384(51.34% R9.29[-1.43,20.0]) winrate black , white = 52.32% , 47.68% T2,b2000,1383 - 247 - 1370(50.24% R1.64[-9.25,12.53]) winrate black , white = 52.05% , 47.95% T2,b4000,1408 - 239 - 1353(51.0% R6.92[-3.95,17.8]) winrate black , white = 53.21% , 46.79% ⇨ 誤差? ⇨ 気分悪いので適用せんとこ。
// V830e
- Addition of new scaling comments : https://github.com/official-stockfish/Stockfish/commit/6db47ed71aac3b1667dd68a08c39bfde0fe0a2ab engine1 = YaneuraOuNNUE_V830_1024d.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V830_1024e.exe , eval = tanuki20240131 T2,b1000,1442 - 152 - 1406(50.63% R4.39[-6.32,15.1]) winrate black , white = 51.23% , 48.77% T2,b2000,1405 - 207 - 1388(50.3% R2.11[-8.7,12.93]) winrate black , white = 53.42% , 46.58% ⇨ 誤差なので採用する。
// V830d
-
Remove rootDelta branch : https://github.com/official-stockfish/Stockfish/commit/8e1f273c7d10e2b49c07cdc16b09a3d4574acf4c
-
Tweak return value in futility pruning : https://github.com/official-stockfish/Stockfish/commit/d0b9411b8275369074bb0de041257db2bccc6430
engine1 = YaneuraOuNNUE_V830_1024c.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V830_1024d.exe , eval = tanuki20240131 T2,b1000,1435 - 167 - 1398(50.65% R4.54[-6.2,15.27]) winrate black , white = 52.88% , 47.12% ⇨ b2000でも計測する。 T2,b2000,1395 - 229 - 1376(50.34% R2.38[-8.47,13.24]) winrate black , white = 52.04% , 47.96% ⇨ 誤差なので採用する。
note : 学習時に使うLearner::searchの方でrootDelta初期化してなかった?
// V830c
-
Simplify Away TT Cutoff Return Value Adjustments : https://github.com/official-stockfish/Stockfish/commit/5e98a4e43dd1c2698162bc3f848a0a98943f86c6
engine1 = YaneuraOuNNUE_V830_1024b.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V830_1024c.exe , eval = tanuki20240131 T2,b1000,1430 - 188 - 1382(50.85% R5.93[-4.85,16.71]) winrate black , white = 53.88% , 46.12% ⇨ 強くなってない? b2000でも計測する。 T2,b2000,1380 - 218 - 1402(49.6% R-2.75[-13.58,8.09]) winrate black , white = 50.75% , 49.25% ⇨ 誤差なので採用する。
-
apply const to prefetch parameter : https://github.com/official-stockfish/Stockfish/commit/b0287dcb1c436887075962b596cf2068d2ca9ba8
-
"compiler"コマンドの表示、刷新。
-
__INTEL_COMPILER ⇨ __INTEL_LLVM_COMPILER に変更。
#if defined(__INTEL_COMPILER)
// 最適化でprefetch命令を削除するのを回避するhack。MSVCとgccは問題ない。
__asm__("");
#endif
⇨ __INTEL_COMPILER ⇨ これ対象外になったんか?滅んだのか? ⇨ 滅んだわけではなさそう。いいや、これはこれで。
// V830b
-
Functional simplification in the transposition table : https://github.com/official-stockfish/Stockfish/commit/d1a71fdaa7cc7d749495bbf5d63919a4a0b42303
-
Add extension condition to cutoffCnt : https://github.com/official-stockfish/Stockfish/commit/a77a895c3b7460f86b11a3ddfe3528f5be1276b9
- Decrease cutoffCnt increment by 1 if extension is 2 or greater.
engine1 = YaneuraOuNNUE_V830_1024a.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V830_1024b.exe , eval = tanuki20240131 T2,b1000,1379 - 174 - 1447(48.8% R-8.36[-19.11,2.39]) winrate black , white = 51.17% , 48.83%
// 現時点のものをV830aとする
やねうらお — 今日 14:23 ■ やねうら王の実行ファイル多すぎ問題
やねうら王、CPUごとに実行ファイル分かれてる上に、評価関数ごとにも分かれてて、これはなんとかしないと非常にまずい状態。(評価関数 主要なのが6個とCPUアーキテクチャが主要なの8個ほどある。これだけで48個も実行ファイルがある。)
⇨ CPU自動判別してかつ評価関数に応じた実行ファイルを起動してくれるか、差し替えてくれるか何かしてくれる何かを用意すべき。
■ 来週の予定 ・M2 Mac mini買う ・M1以降、NPU(Neural Processing Unit)がついてるらしいのでふかうら王を動かす ・Electron将棋 + Mac用やねうら王/ふかうら王 + モデルファイル の 3つをバンドルして配布する(やねうら王支援者向け?)
- WikiトップページとGitHubのトップページ調整。
- clangでevallearn版のビルドで警告がでる件、修正。その3。
learn/learner.cpp:1512:15: warning: use of infinity is undefined behavior due to the currently enabl 1512 | best_loss = std::numeric_limits::infinity(); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (latest_loss < best_loss) {
cout << " < best (" << best_loss << "), accepted" << endl;
best_loss = latest_loss;
best_nn_directory = Path::Combine((std::string)Options["EvalSaveDir"], dir_name);
trials = newbob_num_trials;
} else {
のようにbest_lossは、lossの最小値を記録しておくための変数で、infinityで初期化しているのは単にある程度大きな値で初期化しておけば、実際のlossはそれよりは小さな値であるから、うまく更新されるだろうという感じのコードでございました。(lastest_lossがINFになることは普通はないと思うので..)
そんなわけで、best_lossの初期値は別に99999とかその程度でも問題なくて、 best_loss = std::numeric_limits::max(); に変更しようと思いました。🙆
-
workflowからg++10,g++11を削除。g++12,g++13を追加。clang 11~14を削除。clang 15を残して、clang 18を追加。
-
makefile macos対応。
-
evallearnを指定してClangでコンパイルしたときにwarningが出ていたの修正。その2。
-
evallearnを指定してClangでコンパイルしたときにwarningが出ていたの修正。
- MSYS2+clang18でビルドした実行ファイルが落ちる件に対応
-
std::chrono::high_resolution_clock::now()を呼び出すとセグフォになる。 ⇨ 代わりにsteady_clockを用いるようにした。 std::chrono::steady_clock::now()
note: https://github.com/yaneurao/YaneuraOu/issues/278
ビルド時にstdlibをlibc++に指定すると実行可能なのでlibstdc++に問題があるということなのでしょうか。
-
- eval_learn版、version文字列が数字以外だとコンパイルエラーになるの修正。
-
GitHubのreleasesのところからWindows用の実行ファイルをdownloadできるようにした。
-
ふかうら王、最大GPUの数を設定できるように。
- コンパイルオプションでMAX_GPU=32のように指定できるようにした。
- YANEURAOU_ENGINE_NNUE_HALFKP_1024X2_8_64 : NNUE型評価関数(halfkp_1024x2-8-64) 対応
- このアーキテクチャが使われているのは、tanuki-wcsc34の評価関数
- PV mate拡張する。上位4手ずつBFSで調べる。
PV_Mate_Search_Threads 2
UCT_Threads1 2
isready
usinewgame
position startpos moves 7g7f 8c8d 6i7h 9c9d 9g9f 8d8e 8h7g 6c6d 7i6h 4a3b 1g1f 1c1d 2g2f 3c3d 3i3h 3a4b 7g2b+ 3b2b 6h7g 7a6b 2f2e 4b3c 3g3f 6a5b 5i6h 7c7d 4g4f 2b3b 2i3g 5a4b 3h4g 8a7c 4i4h 6b6c 2h2i 5b6b 4g5f 8b8a 6g6f 6c5d 6h7i 5d6c 7i8h 6c5d 5f6g 5d5e 3f3e 3d3e 3g4e 3c4d 2e2d 2c2d 6g5f 5e4f 2i2d P*2c 2d2f 3e3f P*3c 2a3c 4e3c+ 3b3c 2f3f 4d3e 3f3h P*3f P*4g 3f3g+ 4h3g 4f5g+ P*5d 5c5d N*4f 3e4f 3g4f P*3g 3h2h N*8f 8g8f 8e8f 7g8f 8a8f P*8g 8f7f S*7g 7f7e N*4e 3c3b B*8f N*6c 8f7e 6c7e R*2a 4c4d 2a9a+ 4d4e L*4d 4b3c 4f4e B*6i P*7f N*3f 2h1h 5g5f 7f7e S*3d N*2e 3d2e 9a1a 5f5e 4e5e 5d5e 1a4a P*8f L*3e N*3d 7g8f P*4h 7h6h 6i4g+ 3e3d 2e3d S*4c 3b4c 4d4c+ 3d4c G*3e S*3d P*4d 4c5b 4a3a L*3b 3e3d 3c3d 3a3b 3d4e S*5h B*7f 5h4g 7f3b L*4f 4e5d 4d4c+ 5b4c 7e7d P*8e 7d7c+ 8e8f 7c6b 8f8g+ 8h8g P*8f 8g8f S*7d B*7b 5d5c 6b6c 7d6c N*4e 5c6b 7b6c+ 6b6c N*7e 6c5d S*6c 5d4d 4g3f G*3e 4e5c+ 3e4f 1h4h P*4g 5c4c 4d4c 3f3e P*8e 8f8e L*8a P*8b R*8h N*8f S*7c P*4d 4c3c G*4c 3b4c 4d4c+ 3c4c S*9c 8a8b P*8d L*8a 7e8c+ 8b8c B*6a 4c3b 8d8c+ N*7a P*3c 3b2b 6a7b+ 8a8c P*8d 8c8d 9c8d 7c8d 8e8d 8h8f+ L*8e N*8a 3c3b+
go btime 6000 wtime 9000 binc 1000 winc 1000
- PVのmove_count >= 1000の時だけfpu_reductionをrootと同じにする。
// rootNodeであるか?
const bool root_or_pv = parent == nullptr || (pv && current->move_count >= 1000);
-
PVではfpu_reductionをrootと同じにする。
PV mate fpu VS PV mate 1m1f 21-9-47 ⇨ 大敗した。
-
ふかうら王、leaf nodeで不詰が証明されていたら詰みチェックを端折るようにした。
- CPU負荷、あまり変わらない。ちょっと無駄だったかも…。
// ループの外でsqrtしておく。
// sumは、double型で計算しているとき、sqrt()してからfloatにしたほうがいいか?
const float sqrt_sum = sqrtf((float)sum);
float c, fpu_reduction, parent_q, init_u;
if (pv && !root)
{
// rootからの手数で按分する。
}
else {
c = root ?
FastLog((sum + options.c_base_root + 1.0f) / options.c_base_root) + options.c_init_root :
FastLog((sum + options.c_base + 1.0f) / options.c_base) + options.c_init;
fpu_reduction = (root ? options.c_fpu_reduction_root : options.c_fpu_reduction) * sqrtf(current->visited_nnrate);
parent_q = sum_win > 0 ? std::max(0.0f, (float)(sum_win / sum) - fpu_reduction) : 0.0f;
init_u = sum == 0 ? 1.0f : sqrt_sum;
}
- ふかうら王にPV lineの詰み探索導入。
- エンジンオプション追加。
- PV_Mate_Search_Threads : PV lineの詰み探索のスレッド数
- PV_Mate_Search_Nodes : PV lineの詰み探索の1局面の最大ノード数
- エンジンオプション追加。
- ふかうら王、rootのdf-pnのmateのコード削除。
- エンジンオプション削除
- RootMateSearchNodesLimitオプション削除
- Rootでdf-pnを呼び出していたコード掃除した。
- エンジンオプション削除
- Mate::Dfpn64 Thread.stopを見るのではなく、stopフラグを持つように変更。
PV_Mate_Search_Threads 1
UCT_Threads1 2
isready
usinewgame
position startpos moves 7g7f 8c8d 6i7h 9c9d 9g9f 8d8e 8h7g 6c6d 7i6h 4a3b 1g1f 1c1d 2g2f 3c3d 3i3h 3a4b 7g2b+ 3b2b 6h7g 7a6b 2f2e 4b3c 3g3f 6a5b 5i6h 7c7d 4g4f 2b3b 2i3g 5a4b 3h4g 8a7c 4i4h 6b6c 2h2i 5b6b 4g5f 8b8a 6g6f 6c5d 6h7i 5d6c 7i8h 6c5d 5f6g 5d5e 3f3e 3d3e 3g4e 3c4d 2e2d 2c2d 6g5f 5e4f 2i2d P*2c 2d2f 3e3f P*3c 2a3c 4e3c+ 3b3c 2f3f 4d3e 3f3h P*3f P*4g 3f3g+ 4h3g 4f5g+ P*5d 5c5d N*4f 3e4f 3g4f P*3g 3h2h N*8f 8g8f 8e8f 7g8f 8a8f P*8g 8f7f S*7g 7f7e N*4e 3c3b B*8f N*6c 8f7e 6c7e R*2a 4c4d 2a9a+ 4d4e L*4d 4b3c 4f4e B*6i P*7f N*3f 2h1h 5g5f 7f7e S*3d N*2e 3d2e 9a1a 5f5e 4e5e 5d5e 1a4a P*8f L*3e N*3d 7g8f P*4h 7h6h 6i4g+ 3e3d 2e3d S*4c 3b4c 4d4c+ 3d4c G*3e S*3d P*4d 4c5b 4a3a L*3b 3e3d 3c3d 3a3b 3d4e S*5h B*7f 5h4g 7f3b L*4f 4e5d 4d4c+ 5b4c 7e7d P*8e 7d7c+ 8e8f 7c6b 8f8g+ 8h8g P*8f 8g8f S*7d B*7b 5d5c 6b6c 7d6c N*4e 5c6b 7b6c+ 6b6c N*7e 6c5d S*6c 5d4d 4g3f G*3e 4e5c+ 3e4f 1h4h P*4g 5c4c 4d4c 3f3e P*8e 8f8e L*8a P*8b R*8h N*8f S*7c P*4d 4c3c G*4c 3b4c 4d4c+ 3c4c S*9c 8a8b P*8d L*8a 7e8c+ 8b8c B*6a 4c3b 8d8c+ N*7a P*3c 3b2b 6a7b+ 8a8c P*8d 8c8d 9c8d 7c8d 8e8d 8h8f+ L*8e N*8a 3c3b+ 2b3b L*3d 3b2a 1f1e S*7e
go btime 6000 wtime 9000 binc 1000 winc 1000
position startpos moves 7g7f 8c8d 6i7h 9c9d 9g9f 8d8e 8h7g 6c6d 7i6h 4a3b 1g1f 1c1d 2g2f 3c3d 3i3h 3a4b 7g2b+ 3b2b 6h7g 7a6b 2f2e 4b3c 3g3f 6a5b 5i6h 7c7d 4g4f 2b3b 2i3g 5a4b 3h4g 8a7c 4i4h 6b6c 2h2i 5b6b 4g5f 8b8a 6g6f 6c5d 6h7i 5d6c 7i8h 6c5d 5f6g 5d5e 3f3e 3d3e 3g4e 3c4d 2e2d 2c2d 6g5f 5e4f 2i2d P*2c 2d2f 3e3f P*3c 2a3c 4e3c+ 3b3c 2f3f 4d3e 3f3h P*3f P*4g 3f3g+ 4h3g 4f5g+ P*5d 5c5d N*4f 3e4f 3g4f P*3g 3h2h N*8f 8g8f 8e8f 7g8f 8a8f P*8g 8f7f S*7g 7f7e N*4e 3c3b B*8f N*6c 8f7e 6c7e R*2a 4c4d 2a9a+ 4d4e L*4d 4b3c 4f4e B*6i P*7f N*3f 2h1h 5g5f 7f7e S*3d N*2e 3d2e 9a1a 5f5e 4e5e 5d5e 1a4a P*8f L*3e N*3d 7g8f P*4h 7h6h 6i4g+ 3e3d 2e3d S*4c 3b4c 4d4c+ 3d4c G*3e S*3d P*4d 4c5b 4a3a L*3b 3e3d 3c3d 3a3b 3d4e S*5h B*7f 5h4g 7f3b L*4f 4e5d 4d4c+ 5b4c 7e7d P*8e 7d7c+ 8e8f 7c6b 8f8g+ 8h8g P*8f 8g8f S*7d B*7b 5d5c 6b6c 7d6c N*4e 5c6b 7b6c+ 6b6c N*7e 6c5d S*6c 5d4d 4g3f G*3e 4e5c+ 3e4f 1h4h P*4g 5c4c 4d4c 3f3e P*8e 8f8e L*8a P*8b R*8h N*8f S*7c P*4d 4c3c G*4c 3b4c 4d4c+ 3c4c S*9c 8a8b P*8d L*8a 7e8c+ 8b8c B*6a 4c3b 8d8c+ N*7a P*3c 3b2b 6a7b+ 8a8c P*8d 8c8d 9c8d 7c8d 8e8d 8h8f+ L*8e N*8a 3c3b+ 2b3b L*3d 3b2a 1f1e S*7e 8d9d G*9c
go btime 6000 wtime 9000 binc 1000 winc 1000
isready
position startpos moves 2g2f 8c8d 7g7f 8d8e 8h7g 1c1d 3i3h 7a7b 7i8h
go btime 60000 wtime 60000 binc 1000 winc 1000
PV_Mate_Search_Threads 4
isready
usinewgame
position startpos moves 7g7f 8c8d 7i6h 6c6d 8h7g 3c3d 4g4f 2b7g+ 6h7g 9c9d 3i3h
go btime 59000 wtime 60000 binc 1000 winc 1000
PV_Mate_Search_Threads 2
isready
usinewgame
position startpos moves 7g7f 8c8d 8h7g 4a3b 7i6h 3a4b 3i4h 3c3d 7g2b+ 3b2b 1g1f 1c1d 4g4f 2b3b 9g9f 9c9d 6i7h 7a6b 3g3f 6c6d 2g2f 7c7d 2i3g 4b3c 4h4g 8a7c 4i4h 5a4b 6g6f 6b6c 4g5f 6a6b 2h2i 6c5d 5i6i 8d8e 6h7g 6d6e 6f6e 8e8f 7g8f 5d6e 5f6e 7c6e 6i5h 3d3e 3g4e 3e3f P*3h 3c4d P*6f P*8e S*7a 8e8f 7a8b 8f8g+ 6f6e 8g7h N*3d 4b3a 8b7c+ S*3c 4e3c+ 4d3c S*2b 3b2b R*8a P*6a 7c6b 3c3d 8a6a+ 3a3b 6b5b 2c2d 6a3a 3b2c 3a3d 2c3d B*6g S*4e 6g4e 3d3e S*5e R*6h 5h4g B*1b 4e1b+ 6h4h+ 4g4h G*3g 3h3g 3f3g+ 4h3g S*3f 3g2h G*2g 2h3i N*4g 3i4h 2b1b S*4d 4c4d 5e4d 3e4d G*4c 4d3d
go btime 27000 wtime 17000 binc 1000 winc 1000
- テスト用の局面。9手詰め。
PV_Mate_Search_Threads 2
isready
usinewgame
position startpos moves 2g2f 8c8d 2f2e 4a3b 6i7h 8d8e 3i3h 7a7b 9g9f 9c9d 3g3f 8e8f 8g8f 8b8f 5i6h 1c1d 2i3g 7c7d 2e2d 2c2d 2h2d P*2c 2d7d 8f8b P*8g 7b7c 7d7e 3c3d 7e2e 4c4d 7g7f 3a4b 4g4f 4b4c 3h4g 7c6d 4i4h 6a5b 6h5h 2a3c 2e2i 8b7b 7h7g 5c5d 1g1f 4d4e 3g4e 3c4e 4f4e 6d6e 5g5f 5d5e N*4d 5e5f 6g6f 4c4d 4e4d 6e5d 4g5f 2b4d S*4e P*4g 4h4g 5d4e 5f4e 4d7a 4g5g P*5f 5g5f N*6d 5h6g 6d5f 4e5f N*6d 5f6e 8a7c N*6h 7c6e 6f6e 7a9c 6e6d S*6i N*7e S*5h 6g5f P*5e 5f4f 6c6d 7g6f 5b5c 6f5e P*5d 5e4e 7b8b P*5b 5c5b 8h1a+ 8b8g+ L*7g G*4g 4f5f 6i7h 4e5d 7h7i 5d6d 9c8b N*4d S*5e 5f5e 8b6d 5e6e 6d7e 4d5b+ 5a5b S*6c 5b6c S*7d 6c5b G*6c 5b4b 7f7e P*6d 6c6d N*6a P*5d S*2b B*4d 2b1a 5d5c+ 4b3a 4d1a+ B*5a P*2b 3b2b S*4b 5a4b 5c4b 3a4b 1a2b S*7f 6e5e P*5d 6d5d G*4e 5e4e 8g8c
go
UCT_Threads1 1
PV_Mate_Search_Threads 2
isready
usinewgame
position sfen 1p3+p+pp1/1+Pg4n1/1+Pn2pksp/1Sp1K4/1+r6L/4pG3/PPP2LPPP/1B2R2L1/BN1PP1GNL b g2s 1
go btime 10000 wtime 10000 byoyomi 1000
⇨ 詰みは発見できたものの、指し手がそれにならないなー。なんぞこれ…。タイミングによるんかな…。うーむ..
- mateちゃんと出せるようにする。
- matedちゃんと出せるようにする。
- df-pnの詰み、手数がちゃんと出るようにする。
⇨ mate見つけるとSetLose書き込みしてしまうから、その枝から先に 探索がいかなくなって、古い情報のまま、nodeも展開されないから、 PV lineがそこではなくなって、node展開されてないからPV mateも 機能しなくて、読み筋がいつまでも更新されないのか? ⇨ 違う気がする。よく考える。 ⇨ cp 3000ってeval_coef = 285だと詰みスコアか。そうか…。
PV_Mate_Search_Threads 2 isready usinewgame position sfen 1p3+p+pp1/1+Pg4n1/1+Pn2pksp/1Sp1K4/1+r6L/4pG3/PPP2LPPP/1B2R2L1/BN1PP1GNL b g2s 1 go
PV_Mate_Search_Threads 2
isready
usinewgame
position startpos moves 2g2f 8c8d 2f2e 4a3b 6i7h 8d8e 3i3h 7a7b 9g9f 9c9d 3g3f 8e8f 8g8f 8b8f 5i6h 1c1d 2i3g 7c7d 2e2d 2c2d 2h2d P*2c 2d7d 8f8b P*8g 7b7c 7d7e 3c3d 7e2e 4c4d 7g7f 3a4b 4g4f 4b4c 3h4g 7c6d 4i4h 6a5b 6h5h 2a3c 2e2i 8b7b 7h7g 5c5d 1g1f 4d4e 3g4e 3c4e 4f4e 6d6e 5g5f 5d5e N*4d 5e5f 6g6f 4c4d 4e4d 6e5d 4g5f 2b4d S*4e P*4g 4h4g 5d4e 5f4e 4d7a 4g5g P*5f 5g5f N*6d 5h6g 6d5f 4e5f N*6d 5f6e 8a7c N*6h 7c6e 6f6e 7a9c 6e6d S*6i N*7e S*5h 6g5f P*5e 5f4f 6c6d 7g6f 5b5c 6f5e P*5d 5e4e 7b8b P*5b 5c5b 8h1a+ 8b8g+ L*7g G*4g 4f5f 6i7h 4e5d 7h7i 5d6d 9c8b N*4d S*5e 5f5e 8b6d 5e6e 6d7e 4d5b+ 5a5b S*6c 5b6c S*7d 6c5b G*6c 5b4b 7f7e P*6d 6c6d N*6a P*5d S*2b B*4d 2b1a 5d5c+ 4b3a 4d1a+ B*5a P*2b 3b2b S*4b 5a4b 5c4b 3a4b 1a2b S*7f 6e5e P*5d 6d5d G*4e 5e4e
go
// uct_nodeからPVを辿り、詰み探索をしていない局面を見つけたら
// df-pnで詰探索を1回行う。
// 詰み探索を行う局面がないか、詰探索を1回したならば、returnする。
void PvMateSearcher::SearchInner(Position& pos, Node* uct_node)
{
// 停止
if (stop) return;
// 未展開の場合、終了する
if (!uct_node->IsEvaled() || !uct_node->child) {
std::this_thread::yield();
return;
}
ChildNode* uct_child = uct_node->child.get();
// このnodeがroot nodeでかつ詰み探索がまだであるなら、探索する。
// (dlshogiにはない処理だが、ふかうら王ではPV lineのmate searchを1本化したいのでこうする)
bool root = pos.game_ply() == dl_searcher->pos_root.game_ply();
Node* node;
if (root)
node = uct_node;
else
{
// 訪問回数が最大の子ノードを選択
const auto next_index = select_max_child_node(uct_node);
// 詰みの場合、終了する
if (uct_child[next_index].IsWin() || uct_child[next_index].IsLose()) {
std::this_thread::yield();
return;
}
// まだ子Nodeが生成されていないか?
if (!uct_node->child_nodes || !uct_node->child_nodes[next_index])
return;
// 選んだ手を着手
StateInfo st;
const auto move = uct_child[next_index].move;
pos.do_move(move, st);
// 停止
if (stop) return;
node = uct_node->child_nodes[next_index].get();
}
// いまから詰み探索済みフラグをチェックするのでlockが必要
mtx_searched.lock();
// このchild nodeは詰み探索済みであるか?
if (!node->dfpn_checked)
{
// 詰み探索まだ。
// いったん詰み探索をしたことにする。(他のスレッドがこの局面を重複して探索しないように。)
uct_node->dfpn_checked = true;
mtx_searched.unlock();
// 詰みの場合、ノードを更新
Move move = dfpn.mate_dfpn(pos, node_limit);
if (move) {
// 詰みを発見した。
// rootで詰みを発見したのでメッセージを出力しておく。
if (root)
{
sync_cout << "info string found the mate by df-pn , move = " << to_usi_string(move) << sync_endl;
// moveの指し手をSetLose()する。
for (int i = 0; i < node->child_num; ++i)
if (uct_child[i].move == move)
{
uct_child[i].SetLose();
break;
}
}
else
{
// SetWinしておけばPV line上なので次のUctSearchで更新されるはず。
// ここがrootなら、これでrootは詰みを発見するので自動的に探索が停止する。
// ゆえに、rootであるかの判定等は不要である。
uct_child[next_index].SetWin();
// ⇨ moveの指し手を指したら、子ノードの局面に即詰みがあるということなので
// 現局面は負けの局面であることに注意。
}
}
else if (stop) {
// 途中で停止された場合、未探索に戻す。
std::lock_guard<std::mutex> lock(mtx_searched);
node->dfpn_checked = false;
}
// 探索中にPVが変わっている可能性があるため、ルートに戻る。
}
else {
mtx_searched.unlock();
// 詰み探索済みであることがわかったので、
// 子が展開済みの場合、PV上の次の手へ
if (uct_node->child_nodes && uct_node->child_nodes[next_index])
// 再帰的に子を辿っていく。
SearchInner(pos, uct_node->child_nodes[next_index].get());
else
std::this_thread::yield();
}
}
// --------------------------------------------------------------------
// RootDfpnSearcher : Rootノードでのdf-pn探索用。
// --------------------------------------------------------------------
RootDfpnSearcher::RootDfpnSearcher(DlshogiSearcher* dlshogi_searcher)
{
this->dlshogi_searcher = dlshogi_searcher;
solver = std::make_unique<Mate::Dfpn::MateDfpnSolver>(Mate::Dfpn::DfpnSolverType::Node48bitOrdering);
}
// 詰み探索用のメモリを確保する。
// 確保するメモリ量ではなくノード数を指定するので注意。
void RootDfpnSearcher::alloc(u32 nodes_limit)
{
// 子ノードを展開するから、探索ノード数の8倍ぐらいのメモリを要する
solver->alloc_by_nodes_limit((size_t)(nodes_limit * 8));
}
// 引き分けになる手数の設定
// max_game_ply = 引き分けになるgame ply。この値になった時点で不詰扱い。
void RootDfpnSearcher::set_max_game_ply(int max_game_ply)
{
solver->set_max_game_ply(max_game_ply);
}
// df-pn探索する。
// この関数を呼び出すとsearching = trueになり、探索が終了するとsearching = falseになる。
// nodes_limit = 探索ノード数上限
// Threads.stop == trueになるとdfpn探索を終了する。
void RootDfpnSearcher::search(const Position& rootPos , u32 nodes_limit)
{
searching = true;
mate_move = MOVE_NONE;
mate_ponder_move = MOVE_NONE;
Move move = solver->mate_dfpn(rootPos,nodes_limit);
if (is_ok(move))
{
// 解けたのであれば、それを出力してやる。
// PV抑制するならそれ考慮したほうがいいかも…。
auto mate_pv = solver->get_pv();
std::stringstream ss;
ss << "info string solved by df-pn : mate = " << USI::move(move) << " , mate_nodes_searched = " << solver->get_nodes_searched() << std::endl;
ss << "info score mate " << mate_pv.size() << " pv" << USI::move(mate_pv);
this->pv = ss.str();
mate_move = move;
if (mate_pv.size() >= 2)
mate_ponder_move = mate_pv[1]; // ponder moveも設定しておいてやる。
// 探索の中断を申し入れる。
dlshogi_searcher->search_limits.interruption = true;
}
// デバッグメッセージONであるなら状況も出力してやる。
else if (dlshogi_searcher->search_options.debug_message)
{
// 不詰が証明された
if (move == MOVE_NULL)
{
if (solver->get_nodes_searched() > 1) /* いくらか探索したのであれば */
sync_cout << "info string df-pn solver : no mate has been proven. mate_nodes_searched = " << solver->get_nodes_searched() << sync_endl;
}
else if (solver->get_nodes_searched() >= nodes_limit)
{
sync_cout << "info string df-pn solver : exceeded RootMateSearchNodesLimit. mate_nodes_searched = " << solver->get_nodes_searched() << sync_endl;
}
else if (solver->is_out_of_memory())
{
sync_cout << "info string df-pn solver : out_of_memory. mate_nodes_searched = " << solver->get_nodes_searched() << sync_endl;
}
}
searching = false;
}
// root局面での詰み探索用。
std::unique_ptr<RootDfpnSearcher> root_dfpn_searcher;
// あとで
class RootDfpnSearcher
{
public:
RootDfpnSearcher(DlshogiSearcher* dlshogi_searcher);
// 詰み探索用のメモリを確保する。
// 確保するメモリ量ではなくノード数を指定するので注意。
void alloc(u32 nodes_limit);
// df-pn探索する。
// この関数を呼び出すとsearching = trueになり、探索が終了するとsearching = falseになる。
// nodes_limit = 探索ノード数上限
void search(const Position& rootPos, u32 nodes_limit);
// 引き分けになる手数の設定
// max_game_ply = 引き分けになるgame ply。この値になった時点で不詰扱い。
void set_max_game_ply(int max_game_ply);
// 探索中であるかのフラグ
std::atomic<bool> searching;
// 解けた時の詰みになる指し手とponder move(相手の指し手の予想手)
std::atomic<Move> mate_move , mate_ponder_move;
// 解けた時のPV
std::string pv;
private:
// df-pn探索を行うsolver
std::unique_ptr<Mate::Dfpn::MateDfpnSolver> solver;
DlshogiSearcher* dlshogi_searcher;
};
root_dfpn_searcher = std::make_unique<RootDfpnSearcher>(this);
// ---------------------
// root nodeでのdf-pn
// ---------------------
// root nodeでのdf-pn solverが詰みを見つけているならその読み筋を出力して、それを返す。
if (root_dfpn_searcher->mate_move)
{
// 詰み筋を表示してやる。
if (!search_limits.silent)
sync_cout << root_dfpn_searcher->pv << sync_endl;
ponderMove = root_dfpn_searcher->mate_ponder_move;
return root_dfpn_searcher->mate_move;
}
// root nodeでの詰め将棋ルーチンの呼び出しに関する条件を設定し、メモリを確保する。
void DlshogiSearcher::InitMateSearcher()
{
// -- root nodeでdf-pn solverを呼び出す時。
// メモリを確保(探索ノード数を設定してそれに応じたメモリを確保する)
root_dfpn_searcher->alloc (search_options.root_mate_search_nodes_limit);
// 引き分けになる手数の設定
root_dfpn_searcher->set_max_game_ply(search_options.max_moves_to_draw);
}
// 詰み探索中で、探索し続ければ解けるのかも。
if (root_dfpn_searcher->searching)
return;
else if (thread_id == s + 1)
root_dfpn_searcher->search(rootPos, search_options.root_mate_search_nodes_limit); // df-pnの探索ノード数制限
// root nodeでの詰め将棋ルーチンの呼び出しに関する条件を設定し、メモリを確保する。
void InitMateSearcher();
// root nodeで詰み探索するならそのためのメモリを確保する。
if (dfpn_thread_num)
InitMateSearcher();
// root nodeでの詰み探索用のスレッド数
const int dfpn_thread_num = (search_options.root_mate_search_nodes_limit > 0) ? 1 : 0;
- ふかうら王にPV lineの詰み探索導入。
- エンジンオプション追加。
- PV_Mate_Search_Threads : PV lineの詰み探索のスレッド数
- PV_Mate_Search_Nodes : PV lineの詰み探索の1局面の最大ノード数
- エンジンオプション追加。
### PV_Mate_Search_Threads, PV_Mate_Search_Nodes
PV line(最善応手列上の局面)に対して、専用スレッドを用意して詰み探索を行います。
- PV_Mate_Search_Threads : PV lineの詰み探索のスレッド数
- PV_Mate_Search_Nodes : PV lineの詰み探索の1局面の最大ノード数
⚠ 事前にこのノード数の分だけメモリを確保するので、あまり大きな数値にするとメモリを大量に消費する。
PV_Mate_Search_Threadsに0を指定するとこの機能は無効化されます。
- やねうら王のKPPTとKPP_KKPT用のbuildで、数値のみからなるVERSIONをmakeする時に指定するとうまく文字列が埋め込まれないバグ修正。
is_ok(Move)のコメント修正。(thanks > Lefu777さん)
- emulation版のMSB32/64 で-1をし忘れている #273 // Lefu777さんのプルリク。
- BURNING_BRIDGES_hd2-Final
engine1 = YaneuraOuNNUE_V820c_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V820c.exe , eval = BURNING_BRIDGES_hd2-Final T2,b1000,1608 - 82 - 1310(55.11% R35.61[24.97,46.24]) winrate black , white = 51.51% , 48.49% ⇨ たぶん公開されてるNNUEよりは+R15ぐらいでtanuki-最新モデルよりは-R35ぐらい..
V820c
- Tweak the stats bonus and malus : https://github.com/official-stockfish/Stockfish/commit/d49b3738bc89353a9318d54af400f02c91d2d69a
engine1 = YaneuraOuNNUE_V820b_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V820c_1024.exe , eval = tanuki20240131 T2,b1000,1464 - 179 - 1357(51.9% R13.18[2.42,23.95]) winrate black , white = 51.65% , 48.35%
⇨ stat bonusいじるの影響がでかすぎる。 これrollbackする。他の枝刈りと絡みすぎる。
V820b
- Adjust best value after a pruned quiet move : https://github.com/official-stockfish/Stockfish/commit/0ef5d05102ce0632d7b076706d26eb4eba7fcb70
- PARAM_DEFINE PARAM_FUTILITY_AT_PARENT_NODE_ALPHA ⇨ 削除
// 親nodeでのfutility margin // 重要度 ★★★☆☆ // 元の値 = Stockfish 14 : 112 , Stockfish 16 : 127 , step = 10 // [PARAM] min:100,max:400,step:10,interval:1,time_rate:1,fixed PARAM_DEFINE PARAM_FUTILITY_AT_PARENT_NODE_ALPHA = 127;
engine1 = YaneuraOuNNUE_V820a_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V820b_1024.exe , eval = tanuki20240131 T2,b1000,1426 - 147 - 1427(49.98% R-0.12[-10.82,10.58]) winrate black , white = 52.47% , 47.53% ⇨ 計測できない差だが、探索パラメーターひとつ減ったのでこれは採用する。
V820a : ここまで。
-
ふかうら王、hash 128bitでコンパイルエラーになっていたの修正。
-
README.mdに第二回電竜戦HWTを戦績として追加。
-
定跡のBookDepthLimitでのfilterについて、コメント追加。
-
MSB64 を使うことで香車の効きの最適化 (#272)
engine1 = YaneuraOuNNUE_V811_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V820a_1024.exe , eval = tanuki20240131 T2,b1000,1439 - 170 - 1391(50.85% R5.89[-4.85,16.64]) winrate black , white = 51.66% , 48.34%
改造前よりR5.89 低かった。と言うか、3000局では計測できない差であった。 指し手生成が5%速くなったところで、NPS 1%ぐらいしか高速化しないのでこの差は計測できない。
仕方ないので、depth固定でのbench時に同じ探索ノード数になるかをチェックする。
bench 1024 1 24 default depth
=========================== Total time (ms) : 24567 Nodes searched : 23135171 Nodes_searched/second : 941717
// NPS下がっているがサーマルスロットリングか。 ノード数は変わってないので良しとする。
bench 1024 1 25 default depth
=========================== Total time (ms) : 72002 Nodes searched : 72640562 Nodes_searched/second : 1008868
// 今度は速くなった。ともかくノード数変わってないので良しとする。
NPSはサーマルスロットリングがあって正確に計測できないが、速くなっていたとして0.4%程度。 たぶん遅くはなってなさそう。
- ふかうら王、hash 128bitでコンパイルエラーになっていたの修正。
- ふかうら王V8.00リリース
- やねうら王Wikiのふかうら王のインストール手順、更新。
・GitHubのやねうら王のレーティング差も計測する。
engine1 = YaneuraOuNNUE1024_2024.01.21-cl4_avx2.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_2024.02.16-cl4_avx2.exe , eval = tanuki20240131 T2,b1000,1353 - 130 - 1517(47.14% R-19.88[-30.56,-9.19]) winrate black , white = 51.01% , 48.99%
+R20ぐらいか。そうか…。
V8.10w
- V8.10uのrevert。
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810w_1024.exe , eval = tanuki20240131 T2,b1000,1356 - 119 - 1525(47.07% R-20.4[-31.07,-9.74]) winrate black , white = 50.68% , 49.32% T2,b2000,1331 - 166 - 1333(49.96% R-0.26[-11.33,10.81]) winrate black , white = 53.49% , 46.51% ⇨ トータルでぜんぜんつよなってない。なんでなのだ…。
V8.10v
- Introduce BAD_QUIET movepicker stage : https://github.com/official-stockfish/Stockfish/commit/a5a76a63704009d35997725558dfafd90f5d616f
- Simplify bad quiets : https://github.com/official-stockfish/Stockfish/commit/eec361f64c7d28ff3a5add64bddc5a96800f7fba → 導入した。
engine1 = YaneuraOuNNUE_V810u_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810v_1024.exe , eval = tanuki20240131 T2,b1000,1434 - 170 - 1396(50.67% R4.67[-6.08,15.41]) winrate black , white = 52.76% , 47.24% ⇨ 計測できる差にならんな.. T2,b2000,1376 - 167 - 1457(48.57% R-9.94[-20.68,0.8]) winrate black , white = 51.82% , 48.18% ⇨ 悪くなさそうなので良しとする。
engine1 = YaneuraOuNNUE_V810v_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE1024_2024.02.16-cl4_avx2.exe , eval = tanuki20240131 T2,b1000,1801 - 128 - 1071(62.71% R90.29[79.27,101.31]) winrate black , white = 51.11% , 48.89% ⇨ 大差か..
・GitHubの配布用実行ファイルのビルドをするためのDocker環境用意する。 ⇨ 別に要らんかった。MSYS2なのでDocker化するのあまり効率よくなさそう。
- ふかうら王、logitの計算の時に、policyが負の値を返すとアンダーフローする可能性があったの修正。
- これ、アンダーフローしたところで微小なものはどうせ0と変わらないのでアンダーフローしても探索に影響はないのだが…。
- Softmaxまわりのコメント追加。
V8.10u
- Remove quiet tt move extensions : https://github.com/official-stockfish/Stockfish/commit/c115e5171eed2650b59f9a8da7cd6153e4cff873
// Quiet ttMove extensions (~1 Elo)
// 駒を取らない置換表の指し手に関する延長
// PV nodeでquietなttは良い指し手のはずだから延長するというもの。
else if (PvNode
&& move == ttMove
&& move == ss->killers[0]
&& (*contHist[0])(movedPiece, to_sq(move)) >= 4194)
extension = 1;
- VVLTC search tune : https://github.com/official-stockfish/Stockfish/commit/5c0388310731ba4bf7989210cb69be67b53bb43d ⇨ これは大規模調整なので、採用見送り。
engine1 = YaneuraOuNNUE_V810t_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810u_1024.exe , eval = tanuki20240131 T2,b1000,1452 - 170 - 1378(51.31% R9.09[-1.66,19.83]) winrate black , white = 51.73% , 48.27% T2,b2000,1253 - 190 - 1157(51.99% R13.85[2.2,25.5]) winrate black , white = 53.44% , 46.56% ⇨ これは、よわなってる可能性がある。 ⇨ 長い時間で計測する。
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810u_1024.exe , eval = tanuki20240131 T2,b2000,1332 - 193 - 1475(47.45% R-17.72[-28.51,-6.92]) winrate black , white = 51.87% , 48.13%
V8.10t
-
Adjust best value in main search depending on depth : https://github.com/official-stockfish/Stockfish/commit/91a4cea437fc0ae177808dd0a9ef791f38229c7b
-
Assorted cleanups : https://github.com/official-stockfish/Stockfish/commit/9068fdc57bcaaa142938e18a529761de1063f786 ⇨ Valueをintに変えたことによる掃除。変えてないから採用せず。
-
Remove unnecessary assignments related to adjusted static evaluation : https://github.com/official-stockfish/Stockfish/commit/3d5b16df7c2125ba52a25b3d4b69fc1261c4eb80 ⇨ unadjustedStaticEval、導入してない。あとで考える。
engine1 = YaneuraOuNNUE_V810r_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810t_1024.exe , eval = tanuki20240131 T2,b1000,1381 - 185 - 1434(49.06% R-6.54[-17.31,4.23]) winrate black , white = 51.44% , 48.56%
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810t_1024.exe , eval = tanuki20240131 T2,b1000,1386 - 160 - 1454(48.8% R-8.32[-19.05,2.4]) winrate black , white = 51.13% , 48.87% // V810からあんまつよなってない…。
V8.10s
- Remove check extension : https://github.com/official-stockfish/Stockfish/commit/96837bc4396d205536cdaabfc17e4885a48b0588
// Check extensions (~1 Elo)
// 王手延長
// 注意 : 王手延長に関して、Stockfishのコード、ここに持ってくる時には気をつけること!
// → 将棋では王手はわりと続くのでそのまま持ってくるとやりすぎの可能性が高い。
//
// ※ Stockfish 14では depth > 6 だったのが、Stockfish 15でdepth > 9に変更されたが
// それでもまだやりすぎの感はある。やねうら王では、延長の条件をさらに絞る。
/*
else if (givesCheck
&& depth > 9
// !!重要!!
// この条件、やねうら王では独自に追加している。
// → 王手延長は、開き王手と駒損しない王手に限定する。
// 将棋だと王手でどんどん延長させる局面があり、探索が終わらなくなる。
&& (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move))
)
extension = 1;
*/
// ⇨ Stockfishで削除されたが、王手延長自体は何らかあった方が良い可能性はあるので条件を調整してはどうか。
// Remove check extension : https://github.com/official-stockfish/Stockfish/commit/96837bc4396d205536cdaabfc17e4885a48b0588
V8.10r
- Simplify opponent movecount reduction : Simplify opponent movecount reduction
// Decrease reduction if opponent's move count is high (~1 Elo)
// 相手の(1手前の)move countが大きければ、reductionを減らす。
// 相手の指し手をたくさん読んでいるのにこちらだけreductionするとバランスが悪いから。
if ((ss - 1)->moveCount > 7)
r--;
⇨ 削除
engine1 = YaneuraOuNNUE_V810q_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810r_1024.exe , eval = tanuki20240131 T2,b1000,1391 - 155 - 1454(48.89% R-7.69[-18.41,3.02]) winrate black , white = 53.64% , 46.36%
V8.10q
-
Tweak capture scoring for move ordering : https://github.com/official-stockfish/Stockfish/commit/f2984471c90d82284720f681314ff87bf5fd833c
-
Assorted trivial cleanups : https://github.com/official-stockfish/Stockfish/commit/59691d46a13880534802fe7e610f56813f0e47fc ⇨ doubleExtensionsの変数名をmultipleExtensionsにrename。他は知らん。
engine1 = YaneuraOuNNUE_V810p_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810q_1024.exe , eval = tanuki20240131 T2,b1000,1439 - 149 - 1412(50.47% R3.29[-7.41,13.99]) winrate black , white = 54.3% , 45.7%
V8.10p
-
Introduce Triple Extensions : https://github.com/official-stockfish/Stockfish/commit/f2b6b5cfc9bd27c0595520c96f6e1f8416296f75
-
VVLTC search tune : https://github.com/official-stockfish/Stockfish/commit/ededadcd6f7fbb9eb122f5fe336025cc4b11753b ⇨ パラメーター、大量に変更がある。これはスルー。
engine1 = YaneuraOuNNUE_V810o_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810p_1024.exe , eval = tanuki20240131 T2,b1000,1443 - 151 - 1406(50.65% R4.51[-6.19,15.22]) winrate black , white = 52.97% , 47.03% ⇨ あんまいいことない可能性がある?
V8.10o
-
Simplify LMR condition : https://github.com/official-stockfish/Stockfish/commit/e815227c3081269f7b37538cf5f32c838991db29
-
Simplify the extension formula : https://github.com/official-stockfish/Stockfish/commit/56b342f9b27827e77cb9e898aa2ed69b628d672f
-
Refactor pv printing : https://github.com/official-stockfish/Stockfish/commit/16afec058229067e2f3965672aff450d6a9babc7 ⇨ PV出力のrefactor。探索部にPV出力が移動した。いまさら…。
-
Simplify array initializations : https://github.com/official-stockfish/Stockfish/commit/13eb023fc09343c80c45f51df83a1b9f6401bd35 ⇨ 配列初期化の単純化。強さは変更ないはず。これは、気が向いたらでいいや…。
-
Use ttPv in depth condition of singular extensions : https://github.com/official-stockfish/Stockfish/commit/fcbb02ffdeebb65c970ecf5aebaa3078cdf8f374
engine1 = YaneuraOuNNUE_V810m_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810o_1024.exe , eval = tanuki20240131 T2,b1000,1430 - 131 - 1439(49.84% R-1.09[-11.76,9.58]) winrate black , white = 51.59% , 48.41%
V8.10m
-
Do more double extensions : https://github.com/official-stockfish/Stockfish/commit/37bd1e774ee4eb03e558062284da1e72cbce5a95 ⇨ もっと二重延長を行う。
-
Move perft out of search : https://github.com/official-stockfish/Stockfish/commit/1dfbde2d1056b49442aab9bf5145ad30d0e87dd1 ⇨ 探索部からperft移動。いまさら…。これはスルー。
-
Refactor history score calculation : https://github.com/official-stockfish/Stockfish/commit/3d49a99aaf75f6f44ef6ec5a22b0acd191b8d01e ⇨ 適用済み
if (lmrDepth < PARAM_PRUNING_BY_HISTORY_DEPTH && history < -3645 * depth) continue;
⇓ -3645 ⇨ -4195 に変更。
engine1 = YaneuraOuNNUE_V810l_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810m_1024.exe , eval = tanuki20240131 T2,b1000,1434 - 157 - 1409(50.44% R3.06[-7.66,13.77]) winrate black , white = 51.67% , 48.33% ⇨ 計測できない差
V8.10l
- Remove redundant max operation on lmrDepth : https://github.com/official-stockfish/Stockfish/commit/2b62c4452db5a02c5ed7d4a2b4c4cb1529fb82b7
パラメーターついでに変更しておく。
lmrDepth += history / 7836;
⇓
lmrDepth += history / 6992;
-
Refactor ttPv reduction conditions : https://github.com/official-stockfish/Stockfish/commit/aa15a9179b8cc5598a6bd04b7c2cfeb81282a0c7#diff-da923b7afa45cab7add143c4705b54142e46b2afe9a2627d5fa3b3474bdc8aecR1153 ⇨ 適用済みっぽいのでスルー。
-
Modify ttPV reduction : https://github.com/official-stockfish/Stockfish/commit/28f8663f3947e716fefe392a463060dc12e39849 ⇨ この次のpatch適用済みなのでスルー。
-
VLTC search tune : https://github.com/official-stockfish/Stockfish/commit/a6fd17f27d7675332166e9e6ea8210237281fc77 ⇨ 部分適用するか検討中。
-
VLTC Search parameters tune : https://github.com/official-stockfish/Stockfish/commit/36db936e769a2e7a95fc4032eec3b79251bbaef5 ⇨ 部分適用するか検討中。
-
Refactor get_best_thread : https://github.com/official-stockfish/Stockfish/commit/ad9fcbc4961229286e5043d984dc172d1b0de052 ⇨ これ一つ前のthreadのrefactorをマージしてないのでスルー
engine1 = YaneuraOuNNUE_V810k_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810l_1024.exe , eval = tanuki20240131 T2,b1000,1441 - 132 - 1427(50.24% R1.7[-8.97,12.37]) winrate black , white = 53.77% , 46.23%
V8.10k
- Reduce futility_margin further when improving : https://github.com/official-stockfish/Stockfish/commit/e860f620aa3eb42f8a6be78c030c75855d0c9ff0
engine1 = YaneuraOuNNUE_V810j_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810k_1024.exe , eval = tanuki20240131 T2,b1000,1380 - 149 - 1471(48.4% R-11.09[-21.8,-0.39]) winrate black , white = 51.91% , 48.09% T2,b2000,1393 - 224 - 1383(50.18% R1.25[-9.59,12.1]) winrate black , white = 53.6% , 46.4% ⇨ 長い時間で言うほどつよなってない
V8.10j
-
Refactor ttPv reduction conditions : https://github.com/official-stockfish/Stockfish/commit/aa15a9179b8cc5598a6bd04b7c2cfeb81282a0c7
-
Refactor code for correcting unadjustedStaticEval : https://github.com/official-stockfish/Stockfish/commit/0fbad56c50edab533e5a2f0319016d14e6a9f99c ⇨ unadjustedStaticEval自体、導入をためらっている。
-
Fix mated-in behaviour : https://github.com/official-stockfish/Stockfish/commit/0c7f56dea6ee9a46a4be8481b2316711cb356f02 ⇨ search::nodesをint64からuint64に変更。そのほかは、ちょっと取り込みにくい。
-
Refactor global variables : https://github.com/official-stockfish/Stockfish/commit/a10791095150bf7c020b92be0f55566fe34e9bf2
- global変数の除去。いまごろ?いまさら? これは大リファクタリングになるので採用保留。
-
Introduce static evaluation correction history : https://github.com/official-stockfish/Stockfish/commit/b4d995d0d910044cf4ea2ad3ee30fd1d21070cd8 ⇨ これpawn historyと関連してるから、導入難しい。
-
Tweak usage of correction history : https://github.com/official-stockfish/Stockfish/commit/6f9071c64354a34970e7b5669701d0ad15b7a694 ⇨ pawn history導入しないから要らない。
-
Remove unneeded operator overload macros : https://github.com/official-stockfish/Stockfish/commit/a5f7386efb90d4ade8ed5a12a39d742c4d441c52 ⇨ Valueをintにしたらoperator定義要らんようになるのか…。これもスルー。
-
Use type aliases instead of enums for Value types : https://github.com/official-stockfish/Stockfish/commit/b987d4f0332f57a58157641bf3a6e437133e7879 ⇨ Valueをintに。これも変更点多いわりに棋力に影響しないのでいったんスルー。
-
Change the Move enum to a class : https://github.com/official-stockfish/Stockfish/commit/cafbe8e8e8c26594dd7040788e6f72bc4bc8cfd9 ⇨ Moveをclassに。これ変更点多いわりに棋力に影響しないのでいったんスルー。
engine1 = YaneuraOuNNUE_V810i_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810j_1024.exe , eval = tanuki20240131 T2,b1000,1319 - 123 - 1318(50.02% R0.13[-11.0,11.26]) winrate black , white = 50.63% , 49.37% ⇨ 計測不能な差
V8.10i
- Modify ttPV reduction : https://github.com/official-stockfish/Stockfish/commit/28f8663f3947e716fefe392a463060dc12e39849
- This patch modifies ttPV reduction by reducing 1 more unless ttValue is above alpha.
engine1 = YaneuraOuNNUE_V810h2_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810i_1024.exe , eval = tanuki20240131 T2,b1000,1325 - 119 - 1416(48.34% R-11.54[-22.46,-0.62]) winrate black , white = 52.06% , 47.94% ⇨ つよくなってるっぽい。PV絡みだからsingular extensionと同じ理屈で見かけが強いだけかも?
V8.10h2 SUPER_SORTあり + PARAM_MOVEPICKER_SORT_ALPHA1 = 3500でテスト
engine1 = YaneuraOuNNUE_V810g_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810h2_1024.exe , eval = tanuki20240131 T2,b1000,1380 - 127 - 1393(49.77% R-1.63[-12.48,9.22]) winrate black , white = 52.98% , 47.02% ⇨ 強くはなっていないが悪くはなさそうなのでこれでよしとする。
V8.10h
- Simplification of partial_insertion_sort formula. : https://github.com/official-stockfish/Stockfish/commit/5546bc0a260d9bd01b1ef9d5b6b10fbbcb3a24b0
PARAM_MOVEPICKER_SORT_TH2 /1960/ ⇨ 廃止
// move pickerでsortする係数 (super sort使用時)
// 重要度 ★★★★★
// 元の値 = 3130 , step = 1
// [PARAM] min:0,max:6000,step:500,interval:1,time_rate:1,fixed
PARAM_DEFINE PARAM_MOVEPICKER_SORT_ALPHA1 = 3330;
// move pickerでsortする係数 (super sort使用しない時)
// 重要度 ★★★★★
// 元の値 = 3130 , step = 1
// [PARAM] min:0,max:6000,step:500,interval:1,time_rate:1,fixed
PARAM_DEFINE PARAM_MOVEPICKER_SORT_ALPHA2 = 3500;
こういう定数にした。
まず3330でテスト。
- Lower MultiPV max to MAX_MOVES : https://github.com/official-stockfish/Stockfish/commit/154abb337e8737aedd6def4e7c0ca18bd4737252
V8.10g
- V8.10cを修正した。
V8.10f
- Fix formatting in search.cpp : https://github.com/official-stockfish/Stockfish/commit/0fca5605fa2e5e7240fde5e1aae50952b2612231 ⇨ これ導入しない。
- Tweak static eval history update : https://github.com/official-stockfish/Stockfish/commit/3cfaef74311e943298a9a82bce5717d272338e66
- Mark square_bb() as constexpr : https://github.com/official-stockfish/Stockfish/commit/4ff297a6dfae199571a4f24631a8e970924c8d63 ⇨ これは対応する関数がないので無視。
- Simplify the improving flag calculation : https://github.com/official-stockfish/Stockfish/commit/1fe562fdf32c153f82929660197f8b97469f76b4
- Cleanup comments : https://github.com/official-stockfish/Stockfish/commit/833a2e2bc09e3640440766683043134d72bffd51
engine1 = YaneuraOuNNUE_V810e_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810g_1024.exe , eval = tanuki20240131 T2,b1000,1397 - 123 - 1480(48.56% R-10.03[-20.68,0.63]) winrate black , white = 53.01% , 46.99% T2,b2000,1423 - 172 - 1405(50.32% R2.21[-8.53,12.96]) winrate black , white = 52.05% , 47.95%
V8.10e
- Fix scores from reverse futility pruning : https://github.com/official-stockfish/Stockfish/commit/1a69efbb404fd4389651ab9f45127fb012c0cf94
engine1 = YaneuraOuNNUE_V810d_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810e_1024.exe , eval = tanuki20240131 T2,b1000,1346 - 128 - 1316(50.56% R3.92[-7.16,14.99]) winrate black , white = 52.25% , 47.75% ⇨ 差が小さすぎて計測不能
V8.10d
- Refactor bestvalue adjustment in qsearch : https://github.com/official-stockfish/Stockfish/commit/bab1cc300cbf1929fe42bdbce786a22dd97c8e1b
engine1 = YaneuraOuNNUE_V810c_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810d_1024.exe , eval = tanuki20240131 T2,b1000,1339 - 110 - 1331(50.15% R1.04[-10.02,12.1]) winrate black , white = 53.11% , 46.89% ⇨ 差が小さすぎて計測不能。
V8.10c
- Adjust value returned after TT cutoff : https://github.com/official-stockfish/Stockfish/commit/f388e4180950833a1f79e023c88ff2521c9583b2
engine1 = YaneuraOuNNUE_V810b_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810c_1024.exe , eval = tanuki20240131 T2,b1000,140 - 6 - 74(65.42% R110.76[69.79,151.73]) winrate black , white = 50.47% , 49.53% ⇨ しまった。returnする条件、間違えてた。V8.10gで修正した。
engine1 = YaneuraOuNNUE_V810b_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810g_1024.exe , eval = tanuki20240131 T2,b1000,1446 - 145 - 1409(50.65% R4.5[-6.19,15.2]) winrate black , white = 52.54% , 47.46% ⇨ うーん、よくない。いっかいrollbackしてやりなおす。
修正したので計測しなおし。
engine1 = YaneuraOuNNUE_V810b_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810c_1024.exe , eval = tanuki20240131 T2,b1000,1320 - 125 - 1305(50.29% R1.99[-9.17,13.14]) winrate black , white = 52.61% , 47.39% ⇨ 差が小さすぎて計測不能。
V8.10b
- Fix wrong mate/tb scores from probCut : https://github.com/official-stockfish/Stockfish/commit/3f5adc037e14e40d1e0e1380e3e7c5884ca528ed
engine1 = YaneuraOuNNUE_V810a_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810b_1024.exe , eval = tanuki20240131 T2,b1000,1414 - 128 - 1458(49.23% R-5.32[-15.99,5.34]) winrate black , white = 52.96% , 47.04% ⇨ OK
V8.10a
- Revert "Adjust stand pat in qsearch on pv nodes" : https://github.com/official-stockfish/Stockfish/commit/358a85379094cbffa9d80b443ba63f3066c4cd33
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 engine2 = YaneuraOuNNUE_V810a_1024.exe , eval = tanuki20240131 T2,b1000,1398 - 123 - 1479(48.59% R-9.78[-20.44,0.87]) winrate black , white = 49.25% , 50.75% ⇨ OK
- 評価関数の比較計測
FV_SCALE = 20 YaneuraOuNNUE_V810_1024.exe , Li YaneuraOuNNUE_V810_1024.exe , tanuki20240122 YaneuraOuNNUE_V810_1024.exe , tanuki20240131
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = li engine2 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240122 T2,b1000,442 - 20 - 538(45.1% R-34.14[-52.48,-15.81]) winrate black , white = 51.33% , 48.67% T2,b2000,426 - 41 - 533(44.42% R-38.93[-57.49,-20.37]) winrate black , white = 52.35% , 47.65%
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = li engine2 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 T2,b1000,405 - 19 - 576(41.28% R-61.19[-79.71,-42.67]) winrate black , white = 49.64% , 50.36% T2,b2000,386 - 46 - 568(40.46% R-67.1[-85.94,-48.27]) winrate black , white = 52.41% , 47.59%
engine1 = YaneuraOuNNUE_V810.exe , eval = hao engine2 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240122 T2,b1000,489 - 30 - 481(50.41% R2.87[-15.48,21.21]) winrate black , white = 50.41% , 49.59%
engine1 = YaneuraOuNNUE_V810.exe , eval = hao engine2 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 T2,b1000,413 - 26 - 561(42.4% R-53.21[-71.72,-34.69]) winrate black , white = 49.49% , 50.51% ⇨ 20240131は確かに強いので、定跡掘るの、これに差し替える。また、以降の探索部の調整はこれを基準に行う。
engine1 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240122 engine2 = YaneuraOuNNUE_V810_1024.exe , eval = tanuki20240131 T2,b4000,423 - 45 - 532(44.29% R-39.83[-58.43,-21.22]) winrate black , white = 56.44% , 43.56% ⇨ 確かに長い時間で強い。
-
ふかうら王、Softmaxのmaxの初期値、0.0から-INFに変更。
- モデルによってはPolicy(logit)が負の値を返すことはありえるので。
-
ふかうら王、持ち時間制御その9
以下のところ1/4 ⇨ 1/8に。
// 経過時間がoptimum /8 を超えてるのに残りmaximum時間をすべて用いても訪問数が逆転しない。
// ただしこの時、eval_diffが0.1なら50%というように、eval_diffの値に応じてrest_optimum_poを減らして考える。
if ( elapsed >= optimum / 8
- ふかうら王の Softmax_Temparatureの上限値変更。500 → 10000
- ふかうら王、持ち時間制御その8
- 合流局面、まだ落ちるケースがあったの修正。
検証用局面
multipv 2
DebugMessage true
isready
usinewgame
position startpos moves 7g7f 8c8d 2g2f 8d8e 2f2e 4a3b 8h7g 3c3d 7i8h 2b7g+ 8h7g 3a2b 3g3f 9c9d 9g9f 6c6d 1g1f 1c1d 6i7h 7a6b 2i3g 5a4b 3i3h 2b3c 4g4f 7c7d 3h4g 8a7c 5i6h 6a7b 4i4h 8b8a 4g5f 6b6c 6g6f 6c5d 3g4e 3c2b 7f7e 7d7e 2e2d 2c2d 2h2d 4c4d P*7d 4d4e 4f4e 2b2c 2d2g P*2d 4e4d P*4e 7d7c+
go btime 170000 wtime 199000 binc 4000 winc 4000
- ふかうら王、持ち時間制御その7
-
指し手はkCheckIntervalMsだけ早く指すように修正。
-
合流局面まわり
-
検証用局面
multipv 2
DebugMessage true
isready
usinewgame
position startpos moves 7g7f 4a3b 2g2f 8c8d 2f2e 8d8e 8h7g 3c3d 7i7h 2b7g+ 7h7g 3a2b 1g1f 1c1d 3g3f 5a4b 6i7h 2b3c 5i6h 7a7b 3i3h 6c6d 9g9f 7b6c 9f9e 7c7d 4g4f 8a7c 3h4g 4c4d 2i3g 6a5b 4i5h 6c5d 2h2i 4b3a 5h4h 5b6b 6h7i 8b8a 7i8h 7d7e 7f7e 7c6e 7g6f 3a2b 4h5h 5d4c 7e7d 9c9d 9e9d 8e8f 8g8f 9a9d 9i9d 8a8f P*8g 8f7f 7h6h P*8f 8g8f 7f8f P*8g B*9i 8h9i 8f8g+ L*8h 8g9f P*9g 9f9d 9i9h P*8f B*5a P*9f 9g9f L*8d B*6i 6b6c 2e2d 2c2d P*2c 2b2c 6f7e 6c7d 7e8d 7d8d P*2e 2d2e 3g2e P*2d 2e3c+ 2a3c L*2h N*2e S*9e 8d9e 5a9e+ 9d7d P*7e P*9g 9h9g 7d7e P*7g S*9i 6h7h 9i8h 7h8h L*8b G*7f 7e7a 6g6f 6e7g 7f7g P*7f 7g8f 8b8f 9e8f S*7g 8h7g 7f7g+ 8f7g P*7f 7g9e P*9d 9e6h P*8h 9g8h G*7g 8i7g 7f7g+ 6h7g N*8e P*7f 8e7g+ 8h7g P*7e S*8g 7e7f 8g7f G*8e S*6g 8e7f 6g7f P*7e 7f6g 6d6e 6f6e 4c5d G*8g 5d6e 7g6h 7e7f L*7i S*6f G*7h B*9h 6g6f 6e6f S*8h P*8f 8g8f 9h8i+ 8f8g S*7g 6h5i 7a6b P*6e 4d4e 8h7g 7f7g+ 8g7g 6f7g 7h7g 8i7i S*6h 7i8h N*4d L*4a 4d3b+ 6b3b P*8i 8h8i 6i7h 8i7h 7g7h N*6f 6h6g P*7g 7h7g 6f5h+ 5i5h 4e4f 4g4f 4a4f 5h6h S*8h 7g8g B*4d S*6f S*7i 2i7i
go btime 10000 wtime 10000 byoyomi 10000
8h7iと8h7i+とがある。同玉で合流する。 → 不成だと取る一ではなかったか…。
-
ふかうら王、memcpyにstd::つけ忘れてたの修正。
-
ふかうら王、持ち時間制御その6 fix
- ひとつ前のcommitで配列のアクセス違反になるケースがあったの修正。
検証用局面
multipv 2
DebugMessage true
isready
usinewgame
position startpos moves 7g7f 8c8d 2g2f 4a3b 2f2e 8d8e 8h7g 3c3d 7i7h 2b7g+ 7h7g 3a2b 1g1f 1c1d 3g3f 6c6d 2i3g 7a6b 5i6h 5a4b 6i7h 2b3c 3i3h 6b6c 4g4f 9c9d 9g9f 7c7d 6g6f 6c5d 3h4g 6a6b 3g4e 3c4d 2e2d
go btime 10000 wtime 10000 byoyomi 10000
V779d
- ふかうら王、持ち時間制御その6
- 合流判定追加
合流判定用のテスト局面。
multipv 2
DebugMessage true
isready
usinewgame
position startpos moves 2g2f 8c8d 7g7f 4a3b 2f2e 8d8e 8h7g 3c3d 7i7h 2b7g+ 7h7g 3a2b 4g4f 5a4b 9g9f B*4g B*3h 4g7d+ 3h7d 7c7d 6i7h 7a7b 1g1f 2b3c 3i4h 1c1d 4h4g 9c9d 4i4h 8a7c 6g6f 6c6d 3g3f 6d6e 2i3g 6e6f 4g5f 7b6c 7g6f 6c5d 2h2i 8e8f 8g8f 6a6b 5f4e 5d6e 6f6e 7c6e 4e5f S*3h 4h3h 6e5g+ S*6e B*6f P*5h 5g5f 6e5f 6f9i+ N*5d 4b3a 5d6b+ 9i8i G*7i 8i7i 7h7i 8b6b B*7c 6b6c 7c5a+ G*4a P*6d 6c6d 5a4a 3a4a B*5e 6d6c G*7c 6c6a S*6b 6a8a 2e2d 2c2d 7c8b P*6a 6b5c+ L*5a 3g4e S*4d 4e3c+ 2a3c 8b8a 4d5e S*2c 5a5c 2c3b 4a3b 5f5e B*7g P*6h B*4a G*2c 3b2c R*2a S*2b S*3a N*6g 5i4i S*1c 3a2b 1c2b S*3a S*1c
go btime 100000 wtime 100000 byoyomi 10000
→ 千日手なのでnode作られてなかった…。
multipv 2
DebugMessage true
isready
usinewgame
position startpos moves 7g7f 4a3b 2g2f 8c8d 2f2e 8d8e 8h7g 3c3d 7i7h 2b7g+ 7h7g 3a2b 1g1f 1c1d 3i3h 5a4b 9g9f 7a7b 3g3f 6c6d 5i6h 7c7d 6i7h 8a7c 3f3e 7c6e 7g6f 8e8f 8g8f 8b8f P*8h 7d7e B*5e 7b6c 3h3g B*3c 5e3c+ 2b3c 3e3d 3c3d 6f6e 6d6e B*7g 8f8a N*7c 8a7a 7c6a+ 7a6a 7g1a+ 2a3c 1a1b 6e6f 6g6f P*6g 6h5h 3c4e 3g4f N*5d P*3c 3b3c P*3e 5d6f 5h4h P*3f L*3h S*3g 2i3g 3f3g+ 3h3g
go btime 100000 wtime 100000 byoyomi 10000
→ 合流はしているのだが、桂不成だと同玉を強制できないので評価値と訪問回数に差がある。
-
ふかうら王、MultiPVの時に、各指し手の訪問回数をnodesとして出力するように変更。
-
ふかうら王、持ち時間制御その5
やねうらお — 今日 05:36
MCTSタイプの探索部、持ち時間をうまく制御すると、わりと強くなる。
基本的な考え方は、その1手に関して、思考時間を増やしても指し手に変化がないと思われるなら、早めに切り上げるということだ。
つまり、探索をmax time(その1手で与えられている最大時間)まで行ったに現在の指し手から変化しそうかどうかを確率的に予測できるとよろしい。
また、別の考え方として、現在の指し手が安定しているかという指し手の安定性がある。
MCTSでは訪問回数が一番多い指し手を選ぶが、そのときにbestの指し手のwinrate(期待勝率)とsecondの指し手のwinrateの関係が best winrate > second winrateになってて欲しいのだが、これが逆転しているとまずい。
その場合は、max timeまで使ってでもbest winrate > second winrateになるかを検証すべきである。
安定性という観点からは、best winrate > second winrateかつ、best movecount(bestの指し手の訪問回数) > second movecount * 1.5 みたいに、訪問回数に大きな差があることが求められる。
これ守ったほうが強くなるようである。(dlshogiがこの条件入れてたので、真似てみたら強くなった)
あとは序盤は、どの指し手も同じぐらいの期待勝率の局面がわりとあって、そういう局面でわずかな差を追い求めても仕方がない。それなのに、上のような安定性条件を守ろうとすると、持ち時間を使いすぎてしまう。
そこで序盤は、maximum timeを低めに設定することでこれを回避する。(dlshogiは、20手目までは延長しない。ふかうら王で言うと、maximum timeがoptimum time * 1.0 , 20手目以降が optimum time * 2.0 みたいな感じにしてあって、この問題を回避している。)
ここも真似てみたらいい感じになった。20手目を境に突然 2.0倍にするのはあんまりイケてないので、もう少しなめらかにしたいのだが、どれくらいの値がいいのか実験してパラメーター調整すべきだと思う。ただ、これを適切に調整するための計算資源を持ち合わせていないのでいまふかうら王は目分量で調整している。
やねうらお — 今日 05:42
訪問回数に関しては、探索打ち切りチェックの時に
best movecount > second movecount + 残りpo
だと、最大時間まで探索してもbestとsecondの指し手のmovecountの逆転が起きない。
しかしこの条件は強すぎて、時間を無駄に使っているっぽい。
例えば、勝率差 winrate diff = best winrate - second winrate として、この勝率差がある程度大きいなら、早めに切り上げる(残りpoを小さめに見積もる)みたいなことも考えられる。(これが確率的に安全な条件となるのがどれくらいなのかよくわからない。実験して確かめるべきだと思う。)
持ち時間制御、究極的には、現在のgame ply、best,second movecount, winrate等を入力にして、切り上げるかどうかの判定を行うNNを用意すべきなのかも知れない。
やねうらお — 今日 05:44
切り上げるかどうかの判定を行うNNを用意すべきなのかも知れない。
こういうの何を教師として良いのかはよくわからないけども、例えば、探索を延長したことによって元の指し手より 勝率が +r だけ高い指し手が選べたら、それを利得として、元の指し手と同じ指し手しか選べなかったら、持ち時間使った分だけ損だったので その使った時間に応じたマイナスをして…みたいな感じになるのかな。
やねうらお — 今日 05:50
■ 合流を含む時の持ち時間制御
持ち時間制御をやっていて、嫌らしいのは、合流である。
例えば、22銀と22銀成りでどちらも同金ととられるとする。つまりは同じ変化に合流するわけである。このとき、move countもwinrateもほぼ同じになって最大時間まで探索打ち切りがなされない。非常にもったいない。この2つのPVが合流しているなら、このような時間の使い方は無駄でしかない。
そうは言ってもPVが合流するかの判定が難しい。先のほうで合流する場合、途中で変化できる場合もあるからなおのこと難しい。
とは言っても、PVの2手目で即座に合流しているなら、それは判定できて欲しいところである。
// 例えば、PVの2手目、4手目の局面のhash keyが同じなら..だとか..
この場合、bestとsecondとの比較というよりは、third(3番目の指し手)がbestの指し手を上回るかどうかという話にはなる。
このへんをうまくできるアルゴリズムを考え中だ。
- ふかうら王、持ち時間制御その4
⇓このコード間違ってた。
// optimum,maximum時間の残りが全部 1.0が返ってきた時のeval
WinType second_winrate_maximum_upperbound = (uct_child[second_i].win + rest_maximum_po) /(uct_child[second_i].move_count + rest_maximum_po + delta);
// 最大残りpoを費やしても1番目と2番目の評価値が逆転しない。
// これはもうだめぽ。
if (best_winrate >= second_winrate_maximum_upperbound)
{
if (o.debug_message)
sync_cout << "info string optimum time is over , best_winrate >= second_winrate_maximum_upperbound"
<< " , best_winrate = " << best_winrate
<< " , second_winrate = " << second_winrate
<< " , second_winrate_maximum_upperbound = " << second_winrate_maximum_upperbound
<< " , rest_maximum_po = " << rest_maximum_po << sync_endl;
break;
}
- ふかうら王、持ち時間制御その3
- refactoring
- 以下の条件、削除
WinType second_eval_optimum_upperbound = (wc + rest_optimum_po) /(mc + rest_optimum_po + delta);
// 経過時間がoptimum/5を超えてるのに残りoptimum時間を用いてもevalが逆転しない。
if ( elapsed_from_ponderhit >= optimum/5
&& best_eval > second_eval_optimum_upperbound
)
{
if (o.debug_message)
sync_cout << "info string interrupted by early exit , best_eval > second_eval_optimum_upperbound"
<< " , best_eval = " << best_eval
<< " , second_eval = " << second_eval
<< " , second_eval_optimum_upperbound = " << second_eval_optimum_upperbound
<< " , rest_optimum_po = " << rest_optimum_po << sync_endl;
break;
}
- ふかうら王、持ち時間制御調整2。
⇓削除
// 最適時間の1/3は必ず使うべき。
if (elapsed_from_ponderhit < s.time_manager.optimum() * 1/3)
return ;
⇓こういうの導入した。
for(int i = 1 ; i < 10 ; ++i)
{
// 経過時間がoptimum * i/10 を超えてるのに残りoptimum時間の70%を用いても訪問数が逆転しない。
// ただし訪問数に(11-i)倍以上の差がある場合に限る。
if (rest_optimum_po > 0 /* optimum時間が残っている */
&& elapsed_from_ponderhit >= optimum * i / 10
&& max_searched > second_searched + rest_optimum_po * 0.7
&& max_searched > second_searched * (10 - i)
)
{
if (o.debug_message)
sync_cout << "info string interrupted by early exit"
<< " , max_searched > second_searched + rest_optimum_po * 0.7 , max_searched > second_searched * (10 - " << i << ")"
<< " , max_searched = " << max_searched
<< " , second_searched = " << second_searched
<< " , rest_optimum_po = " << rest_optimum_po << sync_endl;
// 残り時間くりあげて使って、終了すべき。
s.time_manager.search_end = s.time_manager.round_up(elapsed_from_ponderhit);
return;
}
}
⇓よくなかった
// optimum/4以上時間を使っている状態で勝率にeval_delta 以上の差がある。
WinType eval_delta =
s.game_ply < 20 ? 0.10:
s.game_ply < 40 ? 0.13:
0.15;
if ( elapsed_from_ponderhit >= optimum/4
&& max_eval >= second_eval + eval_delta
)
{
if (o.debug_message)
sync_cout << "info string interrupted by early exit , max_eval >= second_eval + " << eval_delta << " , max_eval = " << max_eval << " , second_eval = " << second_eval
<< " , rest_optimum_po = " << rest_optimum_po << sync_endl;
// 残り時間くりあげて使って、終了すべき。
s.time_manager.search_end = s.time_manager.round_up(elapsed_from_ponderhit);
return;
}
以下の時間の切り上げ、よくなかった。
// 経過時間がoptimum/4を超えていて、残りoptimum時間に max_evalより+0.02良いevalが返り続けても逆転しない。
WinType second_eval_optimum_upperbound2 = (wc + rest_optimum_po * std::min(1.0, max_eval + 0.02)) /(mc + rest_optimum_po + delta);
if (rest_optimum_po > 0 /* optimum時間が残っている */
&& elapsed_from_ponderhit >= optimum/4
&& max_eval >= second_eval_optimum_upperbound2
)
{
if (o.debug_message)
sync_cout << "info string interrupted by early exit , max_eval >= second_eval_optimum_upperbound2 , max_eval = " << max_eval << " , second_eval = " << second_eval
<< " , second_eval_optimum_upperbound2 = " << second_eval_optimum_upperbound2
<< " , rest_optimum_po = " << rest_optimum_po << sync_endl;
// 残り時間くりあげて使って、終了すべき。
s.time_manager.search_end = s.time_manager.round_up(elapsed_from_ponderhit);
return;
}
// optimum時間を超えていて、残り時間に max_evalより+0.02良いevalが返り続けても逆転しないなら終了。
WinType second_eval_optimum_upperbound3 = (wc + rest_maximum_po * std::min(1.0, max_eval + 0.02)) /(mc + rest_maximum_po + delta);
if (rest_optimum_po == 0
&& max_eval >= second_eval_optimum_upperbound3
)
{
if (o.debug_message)
sync_cout << "info string optimum time is over , max_eval >= second_eval_optimum_upperbound3 , max_eval = " << max_eval << " , second_eval = " << second_eval
<< " , second_eval_optimum_upperbound3 = " << second_eval_optimum_upperbound3
<< " , rest_optimum_po = " << rest_optimum_po << sync_endl;
// 残り時間くりあげて使って、終了すべき。
s.time_manager.search_end = s.time_manager.round_up(elapsed_from_ponderhit);
return;
}
- やねうら王、持ち時間制御調整。
- ふかうら王の持ち時間制御、刷新する。
- まるごと書き直した。
- FAST_ALLOCのコード掃除。
⇓これ良くない気がしてきた。
// optimumを0%、maximumを100%として、何%ぐらい延長して良いのか。
// 延長度 = evalの差について + 1番目と2番目の訪問回数の比について
// evalの差について = 差が0なら0%。差×r
// 訪問回数の比について = ((2番目の訪問回数/1番目の訪問回数) - k)/(1-k)
// 2番目の訪問回数/1番目の訪問回数 = k1以下のときに 0%になる
// 2番目の訪問回数/1番目の訪問回数 = k2以上のときに 100%になる
// TODO : パラメーターのチューニングすべき。
const float k1 = 0.70000f;
const float k2 = 1.00000f;
const float r = 20.0f; // 勝率0.02の差 = 延長度40%
const float eval_alpha = 0.02f; // evalの差に下駄履きさせる値。微差は拾い上げる考え。
float eval_bonus = std::min(std::max(float((second_eval - max_eval + eval_alpha) * r),0.0f),1.0f);
float visit_bonus = std::max(float( (double(second_searched) / (double(max_searched) + 1) - k1)/(k2-k1)),0.0f);
float bonus = std::max(std::min(eval_bonus + visit_bonus , 1.0f),0.0f);
TimePoint time_limit = (TimePoint)(double(optimum) * (1.0 - bonus) + double(maximum) * bonus);
if (elapsed_from_ponderhit >= time_limit)
{
if (o.debug_message)
sync_cout << "info string interrupted by bonus limit , eval_bonus = " << eval_bonus << " , visit_bonus = " << visit_bonus
<< " , time_limit = " << time_limit << " , max_searched = " << max_searched << ", second_searched = " << second_searched << sync_endl;
// 残り時間くりあげて使って、終了すべき。
s.time_manager.search_end = s.time_manager.round_up(elapsed_from_ponderhit);
return;
}
- 比較用にEXCLUDED_MOVE_FORCE_EVALUATE追加。
- makebook peta_shockコマンド、思考エンジンオプションのBookDirを無視してbook/を対象フォルダにしていたの修正。
⇓ これくらいで調整してみるとどうか。
- V778m 30分+5fに合わせて調整。
int move_horizon;
if (time_forfeit)
move_horizon = MoveHorizon + 40 - std::min(ply , 40);
else
move_horizon = MoveHorizon - std::min(ply , 80);
- V778l
move_horizon = MoveHorizon + 10 - std::min(ply , 100);
- V778k
int move_horizon;
if (time_forfeit)
move_horizon = MoveHorizon + 40 - std::min(ply , 40);
else
// 40手目まで定跡で進行するとして80までに持ち時間の1/3,120までに1/3,160までに1/3を使うぐらいのペースになるように調整。
// +20は調整項。+0だと80で1/2ぐらい使ってしまう。
move_horizon = MoveHorizon + 20 - std::min(ply , 100);
- V778j
// 切れ負けであるか?
bool time_forfeit = limits.inc[us] == 0 && limits.byoyomi[us] == 0;
// 1. 切れ負けルールの時は、MoveHorizonを + 40して考える。
// 2. ゲーム開始直後~40手目ぐらいまでは定跡で進むし、そこまで進まなかったとしても勝負どころはそこではないので
// ゲーム開始直後~40手目付近のMoveHorizonは少し大きめに考える必要がある。逆に40手目以降、MoveHorizonは40ぐらい減らして考えていいと思う。
// 3. 切れ負けでないなら、100手時点で残り60手ぐらいのつもりで指していいと思う。(これくらいしないと勝負どころすぎてからの持ち時間が余ってしまう..)
// (現在の大会のフィッシャールールは15分+inctime5秒とか5分+inctime10秒そんな感じなので、160手目ぐらいで持ち時間使い切って問題ない)
int move_horizon;
if (time_forfeit)
move_horizon = MoveHorizon + 40 - std::min(ply , 40);
else
move_horizon = MoveHorizon - std::min(ply , 100);
■ やねうら王の持ち時間制御について
やねうら王の持ち時間制御について書く。
持ち時間制御とは、各局面の思考開始時にOptimalTime(最適時間)を算出して探索部に返すことを言う。
探索部はそのOptimalTimeをもとに今回の思考時間を決定する。反復深化の時の評価値のぶれが少ないなら早めに切り上げることもある。だいたい平均するとOptimalTimeの7割ぐらいの時間で思考を切り上げるようである。
では、OptimalTimeをどうやって決定しているかと言うと、大雑把には次式である。
まず、自分の指し手があと何手あるのかを決定する。
512手ルールなら思考エンジンオプションでMaxMovesToDrawは512に設定されているから、現在の手数(gamePly)とから算出できる。
restply = (MaxMovesToDraw - gamePly + 1) / 2
次にMoveHorizonという概念がある。これは終局までの平均手数を考慮して決めてある。
MoveHorizon = 160 / 2
この手数ぐらいはこのあと指すと思って時間配分をしなさいという定数である。この両者の小さい方の値が残りの自分の指し手の回数だと見積もっている。
restply2 = min(MoveHorizon , restply)
そして、フィッシャールールの時は、inctimeが手番が来るごとに加算されるから、残り時間は、
resttime = 持ち時間 + restply2 * inctime
となる。
つまり、
OptimalTime = resttime / restply2
みたいな感じになる。
ところが、実際は平均的にはOptimalTimeの7割ぐらいしか使わないので、余ってくる。
どういうことかと言うと、restply2 == MoveHorizonという状況において、
resttime = 持ち時間 + MoveHorizon * inctimeだから、
OptimalTime = 持ち時間 + MoveHorizon * inctime / MoveHorizon = 持ち時間/MoveHorizon + inctime
となるが、毎回OptimalTimeの7割しか使わないと仮定すると、この2手先では持ち時間は前回のOptimalTime * 0.7だけ減っていることになって、2手先では、
OptimalTime = (持ち時間 - 前回のOptimalTime * 0.7)/MoveHorizon + inctime
となる。これが定常状態になりうる。つまり、右辺の「前回のOptimalTime」と左辺の「(今回の)OptimalTime」が同じ値になって、(残り)持ち時間もずっと同じになった、そういう状態になりうる。右辺のOptimalTime == 左辺のOptimalTimeとしてこの方程式を解くと、
OptimalTime( 1 + 0.7/MoveHorizon) = 持ち時間/MoveHorizon + inctime
となって、持ち時間が減らないような、均衡がとれた状態になってしまう。
この状態は本意ではなく、フィッシャールールであるなら、200手目ぐらいには時間が秒読みぐらいになってても良いのであるから、あまり良い状態とは言えない。
もともとは、30分+inctime 1秒(切れ負け防止のためだけのフィッシャー)みたいなのを想定して上式のようになっていたのだが、最近の大会では、15分+inctime 5秒みたいなのが多くて、別に200手目ぐらいで残り持ち時間30秒ぐらいになってても良くね? みたいなところはある。
そう考えると、上式のままやるとしたら、フィッシャールールのときにgamePlyが増えていった時にMoveHorizonを減らすという補正をするのがお手軽である。
どれくらい増えてった時にどれくらい減らせば良いのかはわからないが、もう少し調整する必要がありそうだ。
- ENGINE_VERSION、makefileの方から変更できるようにした。
- peta_shock_next2で指定された書き出す個数に局面数が満たない時に落ちてたの修正。
- その周りでコンパイル時にwarning出てたの修正。
root(初期局面)での先手の最善手の評価値が47。
先手用の定跡は、先手は最善手を選び、後手は任意(≒ランダム)に選ぶので未解決局面の評価値は47以上となります。
未解決局面763982局面をこの評価値で昇順sortした場合、
10000局面目 .. 評価値70
20000局面目 .. 評価値77
40000局面目 .. 評価値91
80000局面目 .. 評価値112
160000局面目 .. 評価値142
320000局面目 .. 評価値194
640000局面目 .. 評価値292
こんな感じになってました。
例えば、評価値194なのに実は先手悪いみたいな局面があるとして、その局面をさらに掘るためには、⇑のように列挙してsortして、32万局面分掘れば良いということになります。
ただ、普通は上から1万局面ほど掘ると、さらに未解決局面局面がまた出てくるので、それを掘るのを繰り返すので、そうするといつまでも評価値192の先手悪いみたいな局面は掘れないことになります。
そうは言っても評価値 192の局面の方が評価値70の局面よりは良い確率の方がずっと高いはずなので、前者を展開するアルゴリズムにはなかなかしにくい意味はあります。
しかしまあ、終盤の方の評価値 192は信用ならないので、進行度みたいなのを加味して考えないといけないような気がしています。(考え中)
makebook peta_shock_next2 user_book1_20240119080417.db peta_next.sfens 10000 eval_limit 400
makebook peta_shock_next2 user_book1_20240119080417.db peta_next.sfens 2000 eval_limit 400
("startpos moves 2g2f 8c8d 2f2e 8d8e 7g7f 4a3b 8h7g 3c3d 7i6h 2b7g+ 6h7g 3a2b 3g3f 7c7d 3i3h 2b3c 4g4f 8a7c 6i7h 7a6b 3h4g 5a4b 1g1f 6a5a 4i5h", 57)
+ [999] ("startpos moves 2g2f 8c8d 2f2e 8d8e 7g7f 4a3b 8h7g 3c3d 7i6h 2b7g+ 6h7g 3a2b 3i3h 2b3c 6i7h 6c6d 9g9f 1c1d 5i6h 7a6b 4i5h", 36) std::pair<std::string,short>
16279544局面中
先手番用のleaf node : 763982
後手番用のleaf node : 1610163
やねうらお — 今日 12:19
平方根くらいになりますか。
ちょっと面白いデータが得られたので共有します。
先手用定跡は、先手は最善手の評価値と同じ指し手しか選ばない。後手はすべての指し手を選ぶ。
後手用定跡は、後手は最善手の評価値と同じ指し手しか選ばない。先手はすべての指し手を選ぶ。
この条件で、あと、評価値の絶対値が400を超えたらそれは必勝という扱いにして、それは解決済みの変化であるとします。
このとき、先手用定跡のleaf nodeが何局面ぐらいになるのか、要するに解決していない課題局面みたいなのがどれくらいのオーダーであるのかについてです。
いまペタショック定跡は16279544局面登録されているのですが、この定跡に対して、先手用定跡の未解決局面は、763982。後手用定跡の未解決局面は1610163でした。
わりとありますね…。😟
- makebook peta_shock_next2コマンド追加。
コマンド例
makebook peta_shock_next2 user_book1_20240119080417.db peta_next.sfens 1000 eval_limit 400 from_startpos
上のコマンド例の1000は、先手がDBの最善手を選んだ時(後手はDBの任意の指し手)の末端の局面が500個、後手がDBの最善手を選んだ時(先手はDBの任意の指し手)の末端の局面が500である。
上のコマンド例の400は、(ペタショック化した時の)評価値の絶対値が400以内の指し手のみを辿ると言う意味である。
⇓追加した内容。
■ ペタショックNextについて
定跡を掘っていく時に、次に掘るべき局面をどうやって決定するかと言うと、ペタショックNext(コマンド名 makebook peta_shock_next)では、定跡上のPVのleaf moveで進めた局面を選出していた。1つ選出して、そのあと、そのleaf moveが無くなったと仮定して親に評価値を伝播しなおして、またroot(初期局面)からのPV leaf moveを調べて…を繰り返していた。
これは評価関数がある程度信頼できるならうまく機能するのだが、root付近で掘れていない枝が大量に残ることとなった。
そこで、leaf moveにガウスノイズを加えると言うのが改善のアイデアだった。これはうまく機能して、root付近で掘れていない枝はある程度潰すことができた。
しかし、PV leafを掘るというのがあまりいいアイデアではなくて、対策すべきは、相手がどの指し手をやってきてもちゃんと勝負の形になる(信頼できる)leafに辿り着けることである。
そのようなleafとは、つまり、プレイヤーが先手であるとしたら、先手 ⇨ 定跡DBの最善手を選ぶ , 後手 ⇨ 定跡DBのいずれかの指し手を選ぶ というのを繰り返して到達できるleaf nodeの集合であって、それらが信頼できることが大切なのである。
定跡の平均分岐がa手だとすると、30手目でのnodeはa ^ 30あるのだが、先手の最善手が1手のみ、後手の平均分岐がa手だとすると、a ^ 15で済む。これは、a ^ 30の平方根ぐらいで、要するに、その局面ぐらいはちゃんと調べておきましょうということである。
定跡局面が1億だとして、その平方根とは1万局面である。(わりと少ない)
これらを選出して優先して展開すべきではないかと思った。
ただし、展開するとしても、優先順位はつけるべきで、後手にとって評価値の良い順にするだとか、遷移確率の高い順にするだとか、何らか工夫は必要である。
…というのを考え中。
-
peta_shockコマンドの合流チェック、Position::hash_key_after()を使って高速化。
- 使えるの忘れてた。
-
makebook peta_shockにshrinkモード追加。(最善手と同じ評価値の指し手のみを書き出す)
makebook peta_shock user_book1_20240108165930.db user-shrinked.db shrink
やねうらお — 今日 11:09
ShogiBookTools、sort, merge, shrink(最善手と同じ評価値以外の指し手を削除) などのコマンドを実装した。
10億局面に対して行っても物理メモリは100MBほどしか必要としない。🙆
これは次世代の定跡メンテナンスツールなのである。
https://github.com/yaneurao/ShogiBookTools/commit/b41bcb958ff3fe3e31045cbd34a53ee32cd5d421
-
MoveHorizon、40手目までは少し下方修正するようにした。
// 切れ負けルールの時は、MoveHorizonを + 40して考える。 // 逆に、ゲーム開始直後~40手目ぐらいまでは定跡で進むし、そこまで進まなかったとしても勝負どころはそこではないので // ゲーム開始直後~40手目付近のMoveHorizonは少し大きめに考える必要がある。逆に40手目以降、MoveHorizonは40ぐらい減らして考えていいと思う。 const int move_horizon = (time_forfeit ? MoveHorizon + 40 : MoveHorizon) - std::min(ply , 40);
やねうらお — 今日 22:50
定跡にhitして40手目ぐらいまで進んだ時と、定跡にhitしなくて10手目ぐらいで探索しないといけない時とで同じMoveHorizonなの、やっぱりやかしい気がする(勝負どころはいずれも50手目~100手目付近にあるはずで)
そんなわけで、これを考慮したMoveHorizonになるようになるように調整した。
https://github.com/yaneurao/YaneuraOu/commit/35e968eee224c90d069fc1d037a0f94e4e34ce85
- MoveToHorizon 160 → 120に変更。
- SfenPackerでwarning出てたの修正。
やねうらお — 今日 12:35
■ やねうら王のLEARN版について
LEARN版があるのは、TTをスレッドごとに分けないといけないからだ。
TTをなぜスレッドごとに分けないといけないのかと言うと、教師生成の時にTTをスレッド間で共用できないからだ。
教師生成を複数スレッドでやっているのは、教師ファイルがスレッド数だけ書き出されると扱いにくいと思ったからだ。
しかしいまやSSDが普通なのでファイルがいくら分かれようとそれでディスク書き出しが遅くなるわけでもないし、ファイル結合もそんなに時間がかかるわけではないし、1プロセス1スレッドで教師生成して、複数プロセス起動すればいいんじゃないのか、みたいなところはある。
しかしそうすると、NNUEはEvalShareの仕組みがないから、メモリをスレッド数分だけ食うんだよなー。😥
LEARN版、無くしたいのになー。
- 電竜戦HWTで持ち時間の使い方が下手すぎたの修正。
やねうらお — 今日 06:52
■ 電竜戦HWTで持ち時間の使い方が下手すぎる件
残り手数(=MTG)を計算する時、自分手番の残り手数だから、2で割らないといけないのだが、それ割ってなかった。😅
MaxMovesToDrawが256の時はそっちは2で割ってるから(残り)128手でうまく指せていたのだが、MaxMovesToDrawが256の時は256手とMoveToHorizon(=160)のminで160になってて、残り320手に均等に持ち時間配分する感じで指してた。
これだと中盤の難所で時間使わないから弱くなる。
正しくは、このMoveToHorizonも2で割るべきであった。
mate -6 でてたのに途中で違うことになってたやつ。 ponderではちゃんと読めてたのに、ponder外れて読めてないのか…。時間なかったみたいだし、しゃーないか。
>1:ponderhit
<1:info depth 31 seldepth 10 score mate -10 nodes 2254901 nps 4262572 hashfull 1 time 529 pv G*9d P*3c 9d9c 5c9c P*6a P*7c 2e1g+ R*2f 1f2f
<1:bestmove G*9d ponder P*3c
>T:-0094KI,'* 100000 +0033FU -9493KI +5393RY -0061FU +0073FU -2517NK +0026HI -1626TO
<T:-0094KI,T0
>1:position startpos moves 2g2f 8c8d 2f2e 8d8e 7g7f 4a3b 8h7g 3c3d 7i8h 2b7g+ 8h7g 3a2b 3i3h 2b3c 6i7h 7c7d 3g3f 7a6b 2i3g 5a4b 4g4f 1c1d 9g9f 6b7c 4i4h 7c6d 3h4g 6a5b 2h2i 4c4d 4g5f 7d7e 7f7e 6d7e 2e2d 2c2d P*2e 3d3e 2e2d P*2g 2i2g P*2e 3f3e 3c2d B*5e 8b8d 5i6h 8e8f 7g8f 7e8f 8g8f 8d8f P*8g 8f8d S*7e 8d8e P*7f P*3f 3g4e S*2f 2g2i 3f3g+ 4h5h 2f3e 8g8f 8e8c 5e9a+ 4d4e 9a9b P*8b 7e7d N*6d 6h7g 6d5f 5g5f B*3h 2i3i 3h5f+ 7d8c 8b8c 9b8a P*3h 3i5i 3g4g 5h4g 5f4g R*9b S*5h 5i5h 4g5h 8a6c P*6b N*3d 4b3c 6c5b S*4c 5b6a R*6i G*7i 5h6g 7g8h 6g7h 7i7h 3c3d P*6g P*7g 8i7g G*8i 8h8g 3e3f 9b6b+ 3b3c N*5e 8i7i 7h7i 6i7i+ S*8h 7i1i 5e4c+ 3d3e 4c3c 2d3c 4f4e G*8i G*7h 3e2f 6b6f 8i8h 7h8h N*3d S*4g G*3g 6a8c N*4f 4g3f 3g3f 6f5g S*2g 8f8e 3h3i+ 5g5c 2f1g B*7a 3d2f 7a9c+ 3c3d 8g8f 4f3h+ G*8b 1d1e 7g6e 1e1f 6e7c+ 2g2h+ 4e4d 1g2g 8c6a 3h3g 6a3d 3i3h G*9h 1f1g+ S*6b 1i7i 9c6f 3h3i 8f7e 2f3h+ 3d2e 1g1f 2e5b 2a1c 4d4c+ 1c2e 6f1a L*9d 7e8d 7i7f 8h7g 7f6e S*5e P*8a 8b7b 6e5f 8d8c 5f5i 8c9b 9d9f 9b9a 3i2i L*2b P*4b 4c4b 9f9h+ L*9b G*7d 7c7d 5i7i G*2a S*8b 7b8b 7i4i P*9c 4i4b 5b4b 8a8b S*8a G*9d P*3c
>1:go ponder btime 377000 wtime 12000 binc 10000 winc 10000
<1:info depth 26 seldepth 8 score mate -8 nodes 454184 nps 15139466 hashfull 0 time 30 pv 9d9c 5c9c P*6a R*1c 3f2f 6b6a 9h9i
<T:+0063HI,T0
>1:stop
<1:bestmove 9d9c ponder 5c9c
>1:position startpos moves 2g2f 8c8d 2f2e 8d8e 7g7f 4a3b 8h7g 3c3d 7i8h 2b7g+ 8h7g 3a2b 3i3h 2b3c 6i7h 7c7d 3g3f 7a6b 2i3g 5a4b 4g4f 1c1d 9g9f 6b7c 4i4h 7c6d 3h4g 6a5b 2h2i 4c4d 4g5f 7d7e 7f7e 6d7e 2e2d 2c2d P*2e 3d3e 2e2d P*2g 2i2g P*2e 3f3e 3c2d B*5e 8b8d 5i6h 8e8f 7g8f 7e8f 8g8f 8d8f P*8g 8f8d S*7e 8d8e P*7f P*3f 3g4e S*2f 2g2i 3f3g+ 4h5h 2f3e 8g8f 8e8c 5e9a+ 4d4e 9a9b P*8b 7e7d N*6d 6h7g 6d5f 5g5f B*3h 2i3i 3h5f+ 7d8c 8b8c 9b8a P*3h 3i5i 3g4g 5h4g 5f4g R*9b S*5h 5i5h 4g5h 8a6c P*6b N*3d 4b3c 6c5b S*4c 5b6a R*6i G*7i 5h6g 7g8h 6g7h 7i7h 3c3d P*6g P*7g 8i7g G*8i 8h8g 3e3f 9b6b+ 3b3c N*5e 8i7i 7h7i 6i7i+ S*8h 7i1i 5e4c+ 3d3e 4c3c 2d3c 4f4e G*8i G*7h 3e2f 6b6f 8i8h 7h8h N*3d S*4g G*3g 6a8c N*4f 4g3f 3g3f 6f5g S*2g 8f8e 3h3i+ 5g5c 2f1g B*7a 3d2f 7a9c+ 3c3d 8g8f 4f3h+ G*8b 1d1e 7g6e 1e1f 6e7c+ 2g2h+ 4e4d 1g2g 8c6a 3h3g 6a3d 3i3h G*9h 1f1g+ S*6b 1i7i 9c6f 3h3i 8f7e 2f3h+ 3d2e 1g1f 2e5b 2a1c 4d4c+ 1c2e 6f1a L*9d 7e8d 7i7f 8h7g 7f6e S*5e P*8a 8b7b 6e5f 8d8c 5f5i 8c9b 9d9f 9b9a 3i2i L*2b P*4b 4c4b 9f9h+ L*9b G*7d 7c7d 5i7i G*2a S*8b 7b8b 7i4i P*9c 4i4b 5b4b 8a8b S*8a G*9d R*6c
>1:go btime 387000 wtime 12000 binc 10000 winc 10000
<1:info depth 10 seldepth 14 score cp -2574 nodes 770772 nps 22022057 hashfull 0 time 35 pv 9d9c 6c9c+ P*6a 6b6a+ P*4a 4b4a 1f1e 9i9h
<1:bestmove 9d9c ponder 6c9c+
>T:-9493KI,'* 2574 +6393RY -0061FU +6261NG -0041FU +4241UM -1615TO +9998KY
<T:-9493KI,T0
>1:position startpos moves 2g2f 8c8d 2f2e 8d8e 7g7f 4a3b 8h7g 3c3d 7i8h 2b7g+ 8h7g 3a2b 3i3h 2b3c 6i7h 7c7d 3g3f 7a6b 2i3g 5a4b 4g4f 1c1d 9g9f 6b7c 4i4h 7c6d 3h4g 6a5b 2h2i 4c4d 4g5f 7d7e 7f7e 6d7e 2e2d 2c2d P*2e 3d3e 2e2d P*2g 2i2g P*2e 3f3e 3c2d B*5e 8b8d 5i6h 8e8f 7g8f 7e8f 8g8f 8d8f P*8g 8f8d S*7e 8d8e P*7f P*3f 3g4e S*2f 2g2i 3f3g+ 4h5h 2f3e 8g8f 8e8c 5e9a+ 4d4e 9a9b P*8b 7e7d N*6d 6h7g 6d5f 5g5f B*3h 2i3i 3h5f+ 7d8c 8b8c 9b8a P*3h 3i5i 3g4g 5h4g 5f4g R*9b S*5h 5i5h 4g5h 8a6c P*6b N*3d 4b3c 6c5b S*4c 5b6a R*6i G*7i 5h6g 7g8h 6g7h 7i7h 3c3d P*6g P*7g 8i7g G*8i 8h8g 3e3f 9b6b+ 3b3c N*5e 8i7i 7h7i 6i7i+ S*8h 7i1i 5e4c+ 3d3e 4c3c 2d3c 4f4e G*8i G*7h 3e2f 6b6f 8i8h 7h8h N*3d S*4g G*3g 6a8c N*4f 4g3f 3g3f 6f5g S*2g 8f8e 3h3i+ 5g5c 2f1g B*7a 3d2f 7a9c+ 3c3d 8g8f 4f3h+ G*8b 1d1e 7g6e 1e1f 6e7c+ 2g2h+ 4e4d 1g2g 8c6a 3h3g 6a3d 3i3h G*9h 1f1g+ S*6b 1i7i 9c6f 3h3i 8f7e 2f3h+ 3d2e 1g1f 2e5b 2a1c 4d4c+ 1c2e 6f1a L*9d 7e8d 7i7f 8h7g 7f6e S*5e P*8a 8b7b 6e5f 8d8c 5f5i 8c9b 9d9f 9b9a 3i2i L*2b P*4b 4c4b 9f9h+ L*9b G*7d 7c7d 5i7i G*2a S*8b 7b8b 7i4i P*9c 4i4b 5b4b 8a8b S*8a G*9d R*6c 9d9c 6c9c+
>1:go ponder btime 387000 wtime 22000 binc 10000 winc 10000
<1:info depth 21 seldepth 6 score mate -6 nodes 120572 nps 4637384 hashfull 0 time 26 pv P*6a P*7c 8b8c G*1g 2e1g+
<T:+6393RY,T0
>1:ponderhit
<1:bestmove P*6a ponder P*7c
■ やねうら王標準定跡バイナリフォーマットについて
しばらく考えていたのだが、Pythonで書き出す場合、いまどきのSSDは速いので書き出しがボトルネックになってるわけではなさそうなのだ。PackedSfenに変換したりするとそっちの方が遅くなりそうな感じである。
ストレージ、確かにもったいないけど、7zipで圧縮した場合、旧形式の方はテキストで冗長性が高いので1/8ぐらいのサイズになるようである。
それに対して、PackedSfenはハフマン符号化なので、圧縮がほとんど利かない。これでは、旧形式の1/3に収まったとしても圧縮後のサイズで負けてしまう。
そう考えると、バイナリフォーマットみたいな新しい形式に対して諸々のツールセットを実装する手間みたいなものを考えると全く得をしていないとも言えると思う。
いまどきSSDも安いので、23GB(1億局面時)ぐらいどうってことないから、いまの形式のまま行こうと思う。10億局面掘ったところで230GB。7zipで圧縮したら40GB程度。圧縮がかからない80GBよりずっといいと思う。
やねうらお — 今日 05:46
私の実装上はbook_moveのvalueの更新がなくなったら、ループを抜けるようにしています。
ペタショック定跡(現時点で1200万局面程度)、その処理入れてもMAX_PLYまでループ抜けないんですよねー。ループが複雑に絡み合ってて、なんかそういうことになってるんだと思うんですけど、どういう現象なのか詳しいことはよくわからないです。😅
GitHub Sponsorsで支援してくださった方へのNews Letterでペタショック定跡を配布しようと思っているので、よかったら調べてみてくださいな。
やねうらお — 今日 05:53
あと、出次数が0のnodeはそれを親として持つノードは存在しないのでこのループに入る前に削除していけるので、そうやって削除して、それによって出次数が0になったnodeも再帰的に削除して、そうすると、元ある定跡ノード数の1/10程度になります。
なので、このループ自体はMAX_PLY回回ったところで、そこまで時間はかからないです。(1000万局面の定跡DBに対して1分程度)
どちらかと言うと合流チェックにその10倍ぐらいの時間を要してますね…。1局面ずつ全合法手生成して、1手進めてその局面のhash keyが一致する局面があるかをC++のunordered_mapで調べてるだけなんですけど、1000万局面×平均合法手100通り = 10億回それをやる時間が5分ぐらいかかりますね。😢
やねうらお — 今日 14:25 ■ 千日手スコアを0以外に変更する必要性があるのかについて
千日手スコアが0の場合、初期局面で先手+70ぐらいになる。(少なくとも結論は千日手ではなさそうだから)
千日手スコアが先手にとって-50だと、それを回避する手順にしないといけないから、これよりは小さな値となる。先手+40とか30とか。
千日手スコアが先手にとって-100だとおそらく先手は+0にすらできない。マイナスの値となる。
定跡手順の研究には良いのかも知れないけども、大会でこういう定跡が使えるかと言うとちょっと疑問が残るところではある。先手なのに最善の変化が+0の定跡、大会で使いたいかと言うとそれはちょっと…。
いま、ペタショック定跡は1100万局面ほど掘ったところだが、後手だからと言ってそんなに千日手になるわけではなさそう。
floodgateの対局を見ている感じだと定跡を抜けたあとに千日手になることはあるが、定跡レベルで千日手になることはほぼないっぽい。相手が千日手にならない変化を選ぶからというのが大きそう。相手にとっては、探索中はその周辺の局面は評価値0±30ぐらいに見えているので、上振れした評価値(+30)のノードを見つけて、そこを目指そうとするからである。
そんなわけで、千日手スコア0にしているからと言って、実戦で後手が定跡レベルで千日手にしてしまうことはわりと稀で、大会で千日手にしたくないからと言って千日手スコアを変更して定跡を生成する意味があまりなさそうである。
千日手スコアを0から変更しないなら、定跡はflipした局面も共通で良いし、ペタショック化も、先後で異なる局面を書き出さなくていいから処理が単純になり、省メモリで済むし、定跡のバイナリフォーマットもなくてもいいかな、ぐらいの感じになる。いいこと尽くめである。
peta_shock 10020882局面、公開するか? ⇨ 公開できる準備だけ整えておく。
やねうらお — 今日 20:18
■ ペタショック化の時に千日手スコアを0以外に変更する件
書き出す局面数が2倍になるかと思ったのだけど、ほとんどの局面では指し手と評価値は変わらないと思うので、その場合、片側の局面を書き出すだけで良く(やねうら王のFlippedBookオプションがtrueなら、どちらかが書き出されていれば定跡にhitするので)、つまりは、1.1倍ぐらいで済むのではないかと思った。
- peta_shock化、DrawStateの掃除
// 千日手の状態
// 最終的には書き出した定跡DBのdepthに反映させる。
// depth
// +1000 : 先手は千日手を打開できない。
// +2000 : 後手は千日手を打開できない。
// +3000 : 先手・後手ともに千日手を打開できない。
struct DrawState
{
DrawState(u8 state):state(state){}
// bit0 : 先手は千日手を打開する権利を持っていない。
// bit1 : 後手は千日手を打開する権利を持っていない。
// つまり、
// 00b : 先後千日手を打開する権利を持っている。
// 01b : 先手は(後手が千日手を選んだ時に)千日手を打開できない。(後手は打開できる)
// 10b : 後手は(先手が千日手を選んだ時に)千日手を打開できない。(先手は打開できる)
// 11b : 先手・後手ともに千日手を打開できない。
u8 state;
/*
後手番で、11bと00bとの指し手があった時に 後手は00bの指し手を選択するが、親には01b を伝播する。
この時、01bの指し手を選択すると、これがループである可能性がある。
A→B→C→D→A
D→E
となっている場合、後手が誤ったほうを選択するとループになる。
だから、bestの選出は、後手は
00b > 01b > 10b > 11b
先手は、
00b > 10b > 01b > 11b
の順番でなければならない。
*/
// 比較オペレーター
bool operator==(const DrawState& v) const { return state==v.state;}
bool operator!=(const DrawState& v) const { return !(*this==v); }
// 手番cにおいて評価値が同じ時に this のほうが yより勝るか。
bool is_superior(DrawState y, Color c) const
{
// 先手にとってのbestを選出する順番
constexpr int black_[] = {1,3,2,4};
// 後手にとってのbestを選出する順番
constexpr int white_[] = {1,2,3,4};
if (c==BLACK)
return black_[this->state] < black_[y.state];
else
return white_[this->state] < white_[y.state];
}
// 手番cの時、評価値が同じでDrawStateだけ異なる指し手がある時に
// このnodeのDrawStateを求める。
//
// 例)
// 後手番で評価値が同じである 00bと11bの指し手があるとして
// 後手はどちらかを選べるので、後手には千日手の権利があることになる。(先手はそれを決める権利がない)
// ゆえに、このとき、このnodeの draw_stateは、01b となる。
//
// つまり、
// 手番側のbitは、0と1があるなら0(bit and)
// 非手番側のbitは、0と1があるなら1(bit or)
// をすればいいということである。
void select(DrawState y, Color c)
{
*this = select_static(*this, y , c);
}
// ↑のstatic版
static DrawState select_static(DrawState x , DrawState y , Color c)
{
u8 our_bit = c == BLACK ? 1 : 2;
u8 them_bit = ~c == BLACK ? 1 : 2;
return ((x.state & our_bit ) & (y.state & our_bit ))
| ((x.state & them_bit) | (y.state & them_bit));
}
// depthに変換する。
// stateを1000倍しておく。
u16 to_depth() const
{
return state * 1000;
}
};
やねうらお — 今日 19:11
千日手の価値を適切に設定できているのであれば、どれを選んでも同じと見なしていいのかなと思っています。
そうかもですなー。私が最初に実験してた時は、角換わりで評価値が0の局面がわりとあって、そっちにいく指し手を選んで欲しかったので、DrawStateが必要だと思ったんですけど、まあ、千日手自体の価値を変更して(後手は)+200とかにするなら、+200を選ぼうと、評価値+200を選ぼうと、同じ意味ではありますなー。🙄
このDrawStateの実装、わりと面倒でややこしいので、効果に乏しいから削除しますかね…。🙆♂️
-
USE_SFEN_PACKERのシンボルが定義されていない時にPackedSfenをclass定義しないように。
-
PackedSfenの駒落ち、cshogiの出力と完全にバイナリ互換になるように調整した。
- UnitTestにPackedSfenのバイナリチェック追加。
-
PackedSfenの駒落ち対応。
-
PackedSfenの駒落ちのUnitTest追加。
-
is_promoted_piece ⇨ is_non_promotable_piece とrename
-
is_promoted_piece新たに実装。
-
UnitTest通らなくなっていたの修正。
- Position::max_repetition_plyをstatic memberに変更。
- UnitTestの時のランダムプレイヤーによる対局、固定乱数になっていなかったの修正。
やねうらお — 今日 22:20
テラショックコマンド、お掃除した。
他の定跡コマンドも、Pythonで書けるものはPythonで書いた実装にして、速度と省メモリが問われるものは最新の技術で実装しなおして…。🤔
まあいいか、とりあえず、今回はこれだけ。
https://github.com/yaneurao/YaneuraOu/commit/d5adb2db9a7bda92b2c0e8d3a2e145d1fef2b1e2
-
テラショック定跡コマンドの削除
- いまやペタショック定跡コマンドが完全上位互換なので…。
- 例えば、次に掘る局面は makebook peta_shock_nextで求めて、makebook thinkでその局面について思考させる、という動作を繰り返すbatファイルを書けば、それだけで自動定跡掘りができる。
-
テラショック定跡のドキュメント(定跡の作成.mdから削除)
## makebook build_tree
> makebook build_tree read_book.db write_book.db
このコマンドは、テラショック化コマンドと呼ばれるものです。
read_book.dbには、thinkコマンドで実戦で出現した局面に評価値がついているものとして、leaf node(末端の局面)についている評価値を使ってMinimax探索のようなことをして、それぞれの局面での最善手write_book.dbに書き出す機能です。
⚠ IgnoreBookPlyオプションの値は反映されます。これをtrueにしている場合、手数無視で定跡にhitするものとしてtreeを生成します。(write_book.dbに書き出されます) IgnoreBookPlyはtrueにしたほうが良いと思います。
> makebook build_tree read_book.db write_book.db black_contempt 50 white_contempt 150
build_treeで千日手に至るときにそのとき、千日手を評価値いくらにみなすかというのを先手・後手個別に設定できます。
👉 例えば、black_contempt = 50を指定すると先手の千日手時のスコアを-50とみなします。
## makebook extend_tree
> makebook extend_tree read_book.db read_sfen.txt write_sfen.txt
定跡ツリーの延長を行います。
上の例では、read_book.dbの定跡に対して、read_sfen.txtのsfenの局面から開始して定跡DBの末端の局面まで行き、延長する枝をwrite_sfen.txtに書き出します。
延長する枝を選ぶアルゴリズム)
read_sfen.txtの局面から、評価値が先手で-50以上、後手で-150以上の値がついている枝だけを延長していきます。
💡 ここで言う「枝」とは指し手とそこに続く局面のことです。定跡を木のような構造だと見立てているので、このような表現になっています。
💡 ここで書き出されたsfenファイルを、makebook thinkコマンドで思考させるのを繰り返すと定跡ツリーが成長していきます。
read_sfen.txtにはこの延長を行う開始局面を複数指定できます。(わからなければ、"startpos"とだけ書いたテキストを用意すれば、平手の開始局面からになるのでそれで問題ないはずです。)
"startpos moves.."の形で複数の局面を指定します。"sfen ..."や、"sfen ... moves ..."のような形式で指定することもできます。
🌙 このコマンドにIgnoreBookPlyオプションの値は反映されます。
> makebook extend_tree read_book.db read_sfen.txt write_sfen.txt black_eval_limit -200 white_eval_limit -300
extend_treeのときに展開する枝の評価値の下限を先手/後手個別に指定できます。
- black_eval_limit 延長する時の、先手の指し手の評価値の下限
- white_eval_limit 延長する時の、後手の指し手の評価値の下限
> makebook extend_tree read_book.db read_sfen.txt write_sfen.txt extend_range -1 1
extend_rangeを指定すると、その範囲のleaf node(末端の局面)の候補手だけを延長対象とします。
(MultiPVで思考させ複数の指し手が登録されている場合、このextend_rangeの範囲内の候補手で進めた局面だけが延長されます)
extend_range -1 1 と指定したならば、leaf nodeでextend_treeの引き分けの評価値(-1,0,+1)の局面だけを延長します。makebook thinkのときの引き分けの評価値を0にしておけば、千日手局面が延長されます。
👉 引き分けの評価値がついているけども、定跡ツリー上ではまだ千日手のように循環していない場合、延長することによって、そこまでの手順に循環(合流)するので、この延長をさせたいことがあります。
extend_rangeとblack_eval_limit,white_eval_limitの併用は可能です。(leaf以外の局面で、そこを辿るかどうかをblack_eval_limit/white_eval_limitで制限しつつ、leafで延長するところをextend_rangeで制限する)
## makebook endless_extend_tree
> makebook endless_extend_tree book/user_book1.db book/kadai_sfen.txt book/think_sfen.txt depth 8 startmoves 1 moves 32 loop 100 black_eval_limit -50 white_eval_limit -150 nodes 1000000000
makebook extend_treeとthinkを合体させたコマンド。
think_sfen.txtのところは、思考対象局面を書き出す一時ファイル。
- このときbook/kadai_sfen.txtに "startpos" とだけ書いておけば、平手の初期局面が課題局面になるので、そこから自動的に定跡が掘られていきます。
- loopの回数だけ繰り返されます。
- max_game_ply(最大手数)の指定も可能。
- 検討用の局面をbook/kadai_sfen.txtに入れて、そこから定跡を掘るのに使えるかと思います。
例)
思考エンジンを起動して、
Hash 4096
Threads 8
makebook endless_extend_tree book/user_book1.db book/kadai_sfen.txt book/think_sfen.txt depth 36 startmoves 1 moves 70 loop 100 black_eval_limit -50 white_eval_limit -150 nodes 1000000000
みたいな感じで。
🌙 このコマンドにIgnoreBookPlyオプションの値は反映されます。
> makebook endless_extend_tree book/user_book1.db book/kadai_sfen.txt book/think_sfen.txt depth 8 startmoves 1 moves 32 loop 100 black_eval_limit -50 white_eval_limit -150 nodes 1000000000 extend_range -1 1
endless_extendの千日手局面だけを延長したい時。
## makebook s_tera
> makebook s_tera
スーパーテラショック定跡の生成コマンドです。
- スーパーテラショック定跡についてはこちらの記事をご覧ください。👉 [スーパーテラショック定跡が76歩に34歩を全否定](https://yaneuraou.yaneu.com/2021/11/05/super-tera-shock-book/)
⚠ これは、ふかうら王のみ使えるコマンドです。
|パラメーター|意味|
|-|-|
|read_book 【ファイル名】| 前回このコマンドで書きだした定跡ファイル名(デフォルトでは "book/read_book.db")|
|write_book 【ファイル名】| 今回書き出す定跡ファイル名 (デフォルトでは "book/write_book.db")|
| root_sfens_name | 探索開始局面集のファイル名。デフォルトでは"book/root_sfens.txt"。<br/>このファイルがなければ平手の初期局面から。<br/>平手の初期局面をrootとすると後手番の定跡があまり生成されないので(先手の初手は26歩か34歩を主な読み筋としてしまうので)初期局面から1手指した局面をこのファイルとして与えたほうがいいかも?<br/>あと駒落ちの定跡を生成するときは同様に駒落ちの初期局面と、そこから1手指した局面ぐらいをこのファイルで指定すると良いかも。|
|kif_sfens_name | 棋譜のファイル名。この棋譜上の局面かつPVまわりを思考させることもできる。|
|book_save_interval| この局面数だけ思考するごとに定跡ファイルを書き出す。<br/>"book/read_book.db.000001"のようなファイルに通しナンバー付きで書き出していく。</br>このコマンドが終了するときにも書き出すので、数時間に1度保存される程度のペースでも良いと思う。|
|nodes_limit| 1局面について思考するnode数。30knps出るPCで30000を指定すると1局面1秒。|
|think_limit| この局面数思考したらこのコマンドを終了する。|
|search_delta |PVのbest_valueとの差がsearch_deltaのleaf nodeを拾い上げて思考する。<br/>このパラメーターに0を指定すると、その処理はskipされる。|
|search_delta_on_kif | PVのbest_valueとの差がsearch_delta_on_kifのleaf nodeでかつ、<br/>kif_sfens_nameで指定した棋譜上の局面について思考する。<br/>このパラメーターに0を指定すると、その処理はskipされる。|
それぞれのファイルフォーマット
入力)
book/root_sfens.txt
// 探索開始局面
例)
sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1
↑のようなsfen形式か、
startpos moves 7g7f ...
のようなUSIプロトコルの"position"コマンドとして送られる形式でも可。
// この場合、それぞれの局面が探索開始対象となる。
book/kif_sfens.txt
読み込ませたい棋譜。この棋譜上の局面かつ、PVまわりを思考する。
例)
startpos moves 7g7f ...
// このファイルはなければなくとも良い。
book/read_book.db
前回出力された出力ファイル(book/write_book.db)をread_book.dbとrenameしたもの。
// 配置するとその続きから掘ってくれる。
(初回はこのファイルは不要)
出力)
book/write_book.db
→ このあと、このファイルをテラショック化コマンドでテラショック定跡化して使う。
例)
> makebook build_tree book/write_book.db book/user_book1.db
注意点)
高速化のために、局面をSFEN文字列ではなく、局面のhash値で一致しているかどうかをチェックするので
局面のhash値は衝突しないように128bit以上のhashを用いる。(べき)
→ 具体的には、config.hでHASH_KEY_BITSのdefineを128にしてビルドする。
## makebook stera_convert
> makebook stera_convert
スーパーテラショック定跡コマンド("makebook stera")で生成した定跡を超高速にテラショック化するコマンドです。
💡 テラショック化とは、定跡ツリー上のleaf node(末端の局面)の評価値を用いてMinimax探索のようなことをして、一番評価値の良いleaf nodeに到達するような定跡に変換すること。
book/read_book.db
を読み込んで
book/write_tera_book.db
を書き出す。
前者は、スーパーテラショック定跡コマンドで生成された定跡。
(デフォルトでは book/write_book.db として書き出されている)
やねうらお — 今日 20:33 ■ 定跡コマンドの大掃除について
・ペタショック定跡コマンドがテラショック定跡コマンドの完全上位互換になった(と思う)ので、テラショック定跡コマンドを削除する。 ・定跡のマージなどのコマンドはあるが、設計自体が古いので、もうちょっと自由なことができるように実装しなおす。(かも) ⇨ 1億局面ぐらいの局面を扱いたいので、従来のものだと遅い&メモリ消費しすぎるので…。 ・やねうら王に棋譜を与えて、その局面について思考させるコマンド(定跡を作る手助けをするコマンド)があるが、あれ、いまどきはPythonのスクリプト等でやるべきで、やねうら王側にそんなコード残しておくとメンテナンスの手間とかが馬鹿にならない気がしているので削除するかも。
- ExtMoveからMove16へのcastがambiguousでコンパイルエラーになっていたの修正。
ペタショック化コマンド、局面をメモリ上に保持しないことにした。(ファイルに書き出す。定跡DBはsortされていることを前提として良いので、ノードの合流によって局面の増加・減少はないから、書き出した順でSFEN文字列を読み直してそれをそのまま最終的なファイルにSFEN文字列として書き出して良い。)
これで1億局面のペタショック化(定跡ファイル25GB)が35GB程度のメモリで出来るようになった。1億局面まではこれで凌げそう。そこから先はわからん…が、1億局面掘るのに1年ぐらいかかるので、とりあえず、これでしばらく困らなさそう。
https://github.com/yaneurao/YaneuraOu/commit/f5c0e65e363815f8777499918af9d18111efa764 https://github.com/yaneurao/YaneuraOu/commit/5e74e989e37c76b1eb6ac0b87b9b445587a0b418 https://github.com/yaneurao/YaneuraOu/commit/cec894ec03834497441266e43fa3eed2e087d0eb
やねうらお — 今日 05:09 あとは局面のhash keyからBookNode配列へのindexをunordered_mapで持ってて、これがメモリもったいない。こんなの、局面の合流チェックで使うだけなので、配列に持って、hash keyでsortして二分探索すればメモリは節約できる…のだが、O(log N)で、1億局面で27回ぐらいメモリに離散的なアクセスしないといけなくて、合流チェックが27倍ぐらい遅くなりかねない。
いま、1000万局面のペタショック化で合流チェックに5分ぐらいかかっているので、1億局面で50分として、それが27倍になったら、たまらん気もする。😢
そんなわけで、これでもうしばらくいじらんとく。
- peta_shockコマンド、packed_sfenをメモリに持つのやめてテンポラリファイルに書き出し、省メモリ化する。
- 構造体、Move → Move16に変更した。Valueはintからs16に変更。
やねうらお — 今日 04:45 ペタショック化コマンド、局面をメモリ上に保持しないことにした。(ファイルに書き出す。定跡DBはsortされていることを前提として良いので、ノードの合流によって局面の増加・減少はないから、書き出した順でSFEN文字列を読み直してそれをそのまま最終的なファイルにSFEN文字列として書き出して良い。)
これで1億局面のペタショック化(定跡ファイル25GB)が35GB程度のメモリで出来るようになった。1億局面まではこれで凌げそう。そこから先はわからん…が、1億局面掘るのに1年ぐらいかかるので、とりあえず、これでしばらく困らなさそう。
https://github.com/yaneurao/YaneuraOu/commit/f5c0e65e363815f8777499918af9d18111efa764 https://github.com/yaneurao/YaneuraOu/commit/5e74e989e37c76b1eb6ac0b87b9b445587a0b418 https://github.com/yaneurao/YaneuraOu/commit/cec894ec03834497441266e43fa3eed2e087d0eb
sfen文字列の末尾に以前は'0'が書き出されていたが、これを削って出力するようにしたら一致してるかわからなくなったので"sfen"で始まる行の末尾の" 0"を削除するスクリプトを書いた。
def process_file(input_file_path, output_file_path): with open(input_file_path, 'r') as file: lines = file.readlines()
with open(output_file_path, 'w') as file:
for line in lines:
# Check if the line starts with "sfen" and ends with " 0"
if line.startswith("sfen") and line.endswith(" 0\n"):
# Remove the last 2 characters (" 0")
line = line[:-3] + "\n"
file.write(line)
process_file(r'C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\build\NNUE\book\user_book2_1.db', r'C:\Users\yaneen\doc\VSCodeProject\YaneuraOu\YaneuraOu-Dev\build\NNUE\book\user_book2_1a.db')
peta_shock化。
修正後
8630821局面で変換元DBファイル1986MB、変換時の使用メモリ4405MB。hash_key_to_index解放後、3867MB。変換時間、16分20秒。
⇨ なぜかわからんがメモリ余分に食って、時間余計かかるようになっただけなので、やめるか…。
⇨ 指し手重複して登録してるくさいなー。だとして、メモリ使用量減るどころか増えてるので良くない改造か?
⇨ WriteLineのあとFlush()呼び出さないから、buffer食ったままになってたのか?
// 3783MBになった。32*8630821=263MB。4055-263=3792。ぴったり合った。これが原因だったらしい。Step IIで3115MB。hash_key_to_index解放後2575MB。
⇨ これでうまく処理できてたら、採用。readもbufferingオフにすべきではないか。
→ Move16に変更して、Valueをintからs16にした。
3450MB。Step IIで
// 修正前のコードにしたが、4055.4MB。なんか増えてるな…。これでまだバグっておるのか…。hash_key_to_index解放後、3510MB。
修正前
8294952局面で変換元DBファイル1909MB、変換時の使用メモリ3193MB。hash_key_to_index解放後、2759MB。変換時間、15分30秒。
USI_Hash 0
SkipLoadingEval true
makebook peta_shock user_book1_20240108165930.db user_book2.db
write book/user_book2.db
[ PetaShock Result ]
converged_moves : 1395360
retro_counter1 : 1118115
retro_counter2 : 153545
write book nodes : 1271660
- peta_shockコマンド、定跡がsortされているを前提とする。
- sortされてなかったらmakebook sortコマンドを使ってsortしてから適用すること。
- peta_shock_next、packed_sfenを用いるのをやめる。
USI_Hash 0
SkipLoadingEval true
makebook peta_shock_next user_book1_20240108165930.db sfens.txt 1000
⇨ 変更前とぴったり一致した。これで完璧。
# 2024/1/8
- peta_shockコマンド、合流数の表示が0になっていたの修正。
- 一つ前のcommitでコンパイルエラーになっていたの修正。
// 対応するhash keyがbook_nodesに存在するかを高速に判定するために // std::vector のようなbitによる存在チェックを行うためのclass。 class ExistHash { public: ~ExistHash() { release(); }
void reserve(size_t size_mb)
{
release();
// hash_size[MB]だけ割り当てる。
size_t hash_size = 1024 * 1024 * size_mb;
hash = new int8_t[hash_size];
Tools::memclear("exist hash", hash, hash_size);
exist_hash_num = hash_size * 8;
}
void release()
{
if (hash)
delete[] hash;
}
void set(size_t n)
{
size_t index = n % exist_hash_num;
hash[index/8] |= 1 << (index & 7);
}
bool get(size_t n)
{
size_t index = n % exist_hash_num;
return bool(hash[index/8] & (1 << (index & 7)));
}
// 実際に確保したメモリ
int8_t* hash = nullptr;
// 確保されている個数
size_t exist_hash_num = 0;
};
⇨ これよくないアイデアであったか…。
・peta_shockコマンド、合流処理の高速化。
USI_Hash 0
SkipLoadingEval true
makebook peta_shock user_book1_20240108165930.db user_book1.db hash 1024
修正前 time 1:35
修正後 time 2:08
⇨ 遅なった🥲 std::vector<bool>がいかんかったっぽい。int8_tの配列にした。
修正後 time 1:30
8GB割当 time 2:15
⇨ さらに遅なった。メモリのfetch間に合ってなさそう。
⇨ しかも合流チェックのコード、間違っていた。
⇨ これ直したら、合流チェック、めっちゃ時間かかるようになった。flipするからなー。やっぱりflipするの筋が悪いのでは…。
⇨ てかhash_key_to_indexにwhite_hash登録してなかったのか?⇨してた
⇨ 読み込み部、高速化しそう。⇨ した
⇨ 単に合流カウンターのインクリメントがおかしかっただけか。
USI_Hash 0
SkipLoadingEval true
makebook peta_shock user_book1_20240105221623.db user_book1.db
USI_Hash 0
SkipLoadingEval true
makebook peta_shock user_book1_20240108165930.db user_book1.db
⇓
write book/user_book1.db
[ PetaShock Result ]
converged_moves : 9103450
retro_counter1 : 7288654
retro_counter2 : 1002151
write book nodes : 8290805
8294952局面で変換元DBファイル1909MB、変換時の使用メモリ3193MB。hash_key_to_index解放後、2759MB。変換時間、15分30秒。
packed sfen = 32*8.3MB = 265.6MB
hash_key_to_index 434MB/8.3M = 52.28 byte / entry
⇨ これ、hash key(8byte) + index(4byte)でいいなら12byteしか食わないはずなのになー。8+8だとしても、3.25倍食ってるな…。
52-12 = 40byte節約になると…。40 * 8.3M = 332MB節約になるか。わりと大きいか…。
packed sfenを節約するのが先かも知れん。sort前提なら、これ要らんもんなー。
両方削れたとして使用メモリ2596MB。1億局面で31.31GBかー。まあ、そこまではいけるか。
いまのままだとしても、38.5GBいるだけだしなー。そんな変わらんのちゃう?
どうせ1億局面ほどしか掘れんしなー。
1億局面、変換だけで150分かかるから、3時間だなー。
並列化できれば別だけど…。合流チェックは並列化できるか…。
局面の登録も、並列化できるかも知れんが、読み込んだ順は維持しないといけないから、自明ではないな…。
- peta_shockコマンド、sort済みBook DBに対する高速化、省メモリ化。
- memory_saving要らないので削除。
// # NOE:258,SORTED
のように定跡DBの2行目に書いてあれば、sort済みなので出力の時のsortを端折れる。
またNOEはNum Of Entriesの意味で、DB上のSfen局面の数。
これが事前に指定されていれば、固定サイズのメモリ確保で済むので処理が端折れる。
peta shock化、sort済みの定跡に対してはpacked sfenをファイルに書き出して、かつ、hash key to indexを事前に配列に確保して2分探索すれば、後者はN*(8+4) bytesで済むし、局面のpacked sfenなしで良ければ1局面120byteぐらいで済むから、つまりは、N * 132bytesでいいのかな。N=100Mで13.2GB..。そこまで減ってないか?
packed_sfen要らんようなるだけだと、N=100Mで3.2GBの節約にしかならんからか。
これファイルに書き出すだけ野暮かも知れない。
hash key to indexだけどうにかした方がいいか。
# 2024/1/7
V7.78e
- V7.78bにrollbackして、かつ、コメント掃除した。
engine1 = YaneuraOuNNUE_V778a.exe , eval = hao
engine2 = YaneuraOuNNUE_V778e.exe , eval = hao
T2,b1000,296 - 22 - 302(49.5% R-3.49[-26.84,19.87]) winrate black , white = 51.0% , 49.0%
T2,b2000,133 - 15 - 142(48.36% R-11.37[-45.8,23.05]) winrate black , white = 49.45% , 50.55%
⇨ 問題なさげ。
V7.78d
- if (!evaluated)
自体削除してみる。
engine1 = YaneuraOuNNUE_V778a.exe , eval = hao
engine2 = YaneuraOuNNUE_V778d.exe , eval = hao
T2,b1000,647 - 62 - 571(53.12% R21.71[5.31,38.11]) winrate black , white = 52.79% , 47.21%
T2,b2000,314 - 26 - 280(52.86% R19.91[-3.56,43.38]) winrate black , white = 48.82% , 51.18%
⇨ やっぱ、よくないなー。
V7.78c
- Eval::evaluate_with_no_return(pos)が何らかバグってるんじゃ…。
- 遅延evaluate()に変更してみるか。
V7.78b
- Eval::evaluate_with_no_return(pos);
コメントアウトし忘れていた。
engine1 = YaneuraOuNNUE_V778a.exe , eval = hao
engine2 = YaneuraOuNNUE_V778b.exe , eval = hao
T2,b1000,3313 - 293 - 3304(50.07% R0.47[-6.55,7.5]) winrate black , white = 52.73% , 47.27%
T2,b2000,1555 - 191 - 1544(50.18% R1.23[-9.03,11.5]) winrate black , white = 52.92% , 47.08%
V7.78a
- 1手詰めを特殊な条件で見逃すことがあるのを修正。(圧倒的勝勢で次善手を指すので勝敗には影響なさそうなのだが)
engine1 = YaneuraOuNNUE_V778.exe , eval = hao
engine2 = YaneuraOuNNUE_V778a.exe , eval = hao
T2,b1000,2714 - 249 - 2617(50.91% R6.32[-1.51,14.15]) winrate black , white = 52.13% , 47.87%
T2,b2000,1248 - 152 - 1230(50.36% R2.52[-8.95,14.0]) winrate black , white = 50.85% , 49.15%
⇨ 勝敗に影響なさそうなので良しとする。
現象が再現できるUSI入力
isready position startpos moves 7g7f 8b5b 2g2f 5a6b 3i3h 6b7b 5i6h 7b8b 2f2e 3c3d 9g9f 5c5d 6h7h 5d5e 2e2d 2c2d 2h2d 4a3b 2d2h P2c 9f9e 7a7b 6g6f 5e5f 5g5f 5b5f 7h6g 5f5a 7i6h 6c6d 6g7h 3a4b 4g4f 4b5c 3h4g 2b4d 4i5h 5c5d 5h6g 7c7d 2h5h 7b6c 4f4e 4d3c 8h7g 6a7b 4g4f P5e 7h8h 8a7c 6i7h 4c4d 4e4d 3c4d P4e 4d3c 3g3f 6d6e 6f6e 7c8e 7g8f 5d6e P6f 6e5f P5b 5a5b 6g5f 5e5f P5e G4g 5h5f 4g4f 5f4f 5b5e P5f 5e5a 4e4d S6i 7h7i 6i5h+ 4f4e 8c8d 4d4c+ 3c6f S7g 8e7g+ 6h7g 6f5g+ 4c3b S6g G7h P6f N6d 5a6a 6d7b+ 6c7b 4e4c+ 6g7h+ 7i7h N7c 8f5c+ G6b G5b 6b5c 5b6a 7b6a 4c5c G7b G6c 7b6c 5c6c G6b R4b P5b 4b5b+ B7a 6c6b 6a6b G7b 8b8c 5b6b 5g7i 7h7i R*9h go
position startpos moves 7g7f 8b5b 2g2f 5a6b 3i3h 6b7b 5i6h 7b8b 2f2e 3c3d 9g9f 5c5d 6h7h 5d5e 2e2d 2c2d 2h2d 4a3b 2d2h P2c 9f9e 7a7b 6g6f 5e5f 5g5f 5b5f 7h6g 5f5a 7i6h 6c6d 6g7h 3a4b 4g4f 4b5c 3h4g 2b4d 4i5h 5c5d 5h6g 7c7d 2h5h 7b6c 4f4e 4d3c 8h7g 6a7b 4g4f P5e 7h8h 8a7c 6i7h 4c4d 4e4d 3c4d P4e 4d3c 3g3f 6d6e 6f6e 7c8e 7g8f 5d6e P6f 6e5f P5b 5a5b 6g5f 5e5f P5e G4g 5h5f 4g4f 5f4f 5b5e P5f 5e5a 4e4d S6i 7h7i 6i5h+ 4f4e 8c8d 4d4c+ 3c6f S7g 8e7g+ 6h7g 6f5g+ 4c3b S6g G7h P6f N6d 5a6a 6d7b+ 6c7b 4e4c+ 6g7h+ 7i7h N7c 8f5c+ G6b G5b 6b5c 5b6a 7b6a 4c5c G7b G6c 7b6c 5c6c G6b R4b P5b 4b5b+ B7a 6c6b 6a6b G7b 8b8c 5b6b 5g7i 7h7i R*9h 9i9h go
position startpos moves 7g7f 8b5b 2g2f 5a6b 3i3h 6b7b 5i6h 7b8b 2f2e 3c3d 9g9f 5c5d 6h7h 5d5e 2e2d 2c2d 2h2d 4a3b 2d2h P2c 9f9e 7a7b 6g6f 5e5f 5g5f 5b5f 7h6g 5f5a 7i6h 6c6d 6g7h 3a4b 4g4f 4b5c 3h4g 2b4d 4i5h 5c5d 5h6g 7c7d 2h5h 7b6c 4f4e 4d3c 8h7g 6a7b 4g4f P5e 7h8h 8a7c 6i7h 4c4d 4e4d 3c4d P4e 4d3c 3g3f 6d6e 6f6e 7c8e 7g8f 5d6e P6f 6e5f P5b 5a5b 6g5f 5e5f P5e G4g 5h5f 4g4f 5f4f 5b5e P5f 5e5a 4e4d S6i 7h7i 6i5h+ 4f4e 8c8d 4d4c+ 3c6f S7g 8e7g+ 6h7g 6f5g+ 4c3b S6g G7h P6f N6d 5a6a 6d7b+ 6c7b 4e4c+ 6g7h+ 7i7h N7c 8f5c+ G6b G5b 6b5c 5b6a 7b6a 4c5c G7b G6c 7b6c 5c6c G6b R4b P5b 4b5b+ B7a 6c6b 6a6b G7b 8b8c 5b6b 5g7i 7h7i R*9h 9i9h 8d8e go
position startpos moves 7g7f 8b5b 2g2f 5a6b 3i3h 6b7b 5i6h 7b8b 2f2e 3c3d 9g9f 5c5d 6h7h 5d5e 2e2d 2c2d 2h2d 4a3b 2d2h P2c 9f9e 7a7b 6g6f 5e5f 5g5f 5b5f 7h6g 5f5a 7i6h 6c6d 6g7h 3a4b 4g4f 4b5c 3h4g 2b4d 4i5h 5c5d 5h6g 7c7d 2h5h 7b6c 4f4e 4d3c 8h7g 6a7b 4g4f P5e 7h8h 8a7c 6i7h 4c4d 4e4d 3c4d P4e 4d3c 3g3f 6d6e 6f6e 7c8e 7g8f 5d6e P6f 6e5f P5b 5a5b 6g5f 5e5f P5e G4g 5h5f 4g4f 5f4f 5b5e P5f 5e5a 4e4d S6i 7h7i 6i5h+ 4f4e 8c8d 4d4c+ 3c6f S7g 8e7g+ 6h7g 6f5g+ 4c3b S6g G7h P6f N6d 5a6a 6d7b+ 6c7b 4e4c+ 6g7h+ 7i7h N7c 8f5c+ G6b G5b 6b5c 5b6a 7b6a 4c5c G7b G6c 7b6c 5c6c G6b R4b P5b 4b5b+ B7a 6c6b 6a6b G7b 8b8c 5b6b 5g7i 7h7i R*9h 9i9h 8d8e 7b7c go
position startpos moves 7g7f 8b5b 2g2f 5a6b 3i3h 6b7b 5i6h 7b8b 2f2e 3c3d 9g9f 5c5d 6h7h 5d5e 2e2d 2c2d 2h2d 4a3b 2d2h P2c 9f9e 7a7b 6g6f 5e5f 5g5f 5b5f 7h6g 5f5a 7i6h 6c6d 6g7h 3a4b 4g4f 4b5c 3h4g 2b4d 4i5h 5c5d 5h6g 7c7d 2h5h 7b6c 4f4e 4d3c 8h7g 6a7b 4g4f P5e 7h8h 8a7c 6i7h 4c4d 4e4d 3c4d P4e 4d3c 3g3f 6d6e 6f6e 7c8e 7g8f 5d6e P6f 6e5f P5b 5a5b 6g5f 5e5f P5e G4g 5h5f 4g4f 5f4f 5b5e P5f 5e5a 4e4d S6i 7h7i 6i5h+ 4f4e 8c8d 4d4c+ 3c6f S7g 8e7g+ 6h7g 6f5g+ 4c3b S6g G7h P6f N6d 5a6a 6d7b+ 6c7b 4e4c+ 6g7h+ 7i7h N7c 8f5c+ G6b G5b 6b5c 5b6a 7b6a 4c5c G7b G6c 7b6c 5c6c G6b R4b P5b 4b5b+ B7a 6c6b 6a6b G7b 8b8c 5b6b 5g7i 7h7i R*9h 9i9h 8d8e 7b7c 8c8d go btime 555000 wtime 30000 binc 10000 winc 10000
# 2024/1/6
engine1 = YaneuraOuNNUE_V777j4_1024.exe , eval = Suisho_DR4_kishin
engine2 = YaneuraOuNNUE_V777j4_1024.exe , eval = Li
T2,b1000,331 - 24 - 305(52.04% R14.21[-8.45,36.87]) winrate black , white = 52.04% , 47.96%
T2,b1000,323 - 23 - 314(50.71% R4.91[-17.72,27.54]) winrate black , white = 49.61% , 50.39%
T2,b2000,134 - 17 - 159(45.73% R-29.72[-63.17,3.74]) winrate black , white = 53.58% , 46.42%
T2,b2000,144 - 18 - 148(49.32% R-4.76[-38.15,28.64]) winrate black , white = 51.03% , 48.97%
# 2024/1/2
- makebook peta_shock_nextコマンドにfrom_startposと指定して、"startpos moves ..."の形式で次に掘るべき局面を出力する機能を追加。