年末にDiscordのやりとりで、dlshogiの入力特徴量作成で、各マスの利きを求めるために、各マス×手番ごとにattackersToを呼び出すのは無駄と、やねうら氏からご指摘をいただいた。
その通りなので、改善を行った。
改善したコード
各マスの利きをattackersToで調べると、処理の重い飛車・角の飛び利きを81マス×2(手番)回呼び出すことになる。
盤上の駒ごとに利きを調べて、各駒が利いているマスごとに処理することで、飛び利きの呼び出し回数を盤上にある駒の数に減らすことができる。
改善前のコード
const Bitboard occupied_bb = position.occupiedBB(); // 駒の利き(駒種でマージ) Bitboard attacks[ColorNum][PieceTypeNum] = { { { 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 } }, { { 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 } }, }; for (Square sq = SQ11; sq < SquareNum; sq++) { const Piece p = position.piece(sq); if (p != Empty) { const Color pc = pieceToColor(p); const PieceType pt = pieceToPieceType(p); const Bitboard bb = position.attacksFrom(pt, pc, sq, occupied_bb); attacks[pc][pt] |= bb; } } for (Color c = Black; c < ColorNum; ++c) { // 白の場合、色を反転 const Color c2 = turn == Black ? c : oppositeColor(c); // 駒の配置 Bitboard bb[PieceTypeNum]; for (PieceType pt = Pawn; pt < PieceTypeNum; ++pt) { bb[pt] = position.bbOf(pt, c); } for (Square sq = SQ11; sq < SquareNum; ++sq) { // 白の場合、盤面を180度回転 const Square sq2 = turn == Black ? sq : SQ99 - sq; for (PieceType pt = Pawn; pt < PieceTypeNum; ++pt) { // 駒の配置 if (bb[pt].isSet(sq)) { (*features1)[c2][pt - 1][sq2] = 1; } // 駒の利き if (attacks[c][pt].isSet(sq)) { (*features1)[c2][PIECETYPE_NUM + pt - 1][sq2] = 1; } } // 利き数 const int num = std::min(MAX_ATTACK_NUM, position.attackersTo(c, sq, occupied_bb).popCount()); for (int k = 0; k < num; k++) { (*features1)[c2][PIECETYPE_NUM + PIECETYPE_NUM + k][sq2] = 1; } } }
改善後のコード
const Bitboard occupied_bb = position.occupiedBB(); // 歩と歩以外に分ける Bitboard pawns_bb = position.bbOf(Pawn); Bitboard without_pawns_bb = occupied_bb & ~pawns_bb; // 利き数集計用 int attack_num[ColorNum][SquareNum] = {}; // 歩以外 FOREACH_BB(without_pawns_bb, Square sq, { const Piece pc = position.piece(sq); const PieceType pt = pieceToPieceType(pc); Color c = pieceToColor(pc); Bitboard attacks = Position::attacksFrom(pt, c, sq, occupied_bb); // 後手の場合、色を反転し、盤面を180度回転 if (turn == White) { c = oppositeColor(c); sq = SQ99 - sq; } // 駒の配置 (*features1)[c][pt - 1][sq] = 1.0f; FOREACH_BB(attacks, Square to, { // 後手の場合、盤面を180度回転 if (turn == White) to = SQ99 - to; // 駒の利き (*features1)[c][PIECETYPE_NUM + pt - 1][to] = 1.0f; // 利き数 auto& num = attack_num[c][to]; if (num < MAX_ATTACK_NUM) { (*features1)[c][PIECETYPE_NUM + PIECETYPE_NUM + num][to] = 1.0f; num++; } }); }); for (Color c = Black; c < ColorNum; ++c) { // 後手の場合、色を反転 const Color c2 = turn == Black ? c : oppositeColor(c); // 歩 Bitboard pawns_bb2 = pawns_bb & position.bbOf(c2); const SquareDelta pawnDelta = c == Black ? DeltaN : DeltaS; FOREACH_BB(pawns_bb2, Square sq, { // 後手の場合、盤面を180度回転 if (turn == White) sq = SQ99 - sq; // 駒の配置 (*features1)[c][Pawn - 1][sq] = 1.0f; // 駒の利き const Square to = sq + pawnDelta; // 1マス先 (*features1)[c][PIECETYPE_NUM + Pawn - 1][to] = 1.0f; // 利き数 auto& num = attack_num[c][to]; if (num < MAX_ATTACK_NUM) { (*features1)[c][PIECETYPE_NUM + PIECETYPE_NUM + num][to] = 1.0f; num++; } }); }
速度比較
floodgateのR3500同士の棋譜から抽出した6997258局面を使用して、入力特徴量作成処理にかかった時間の合計を測定した。
20回測定した結果は以下の通り。モデルサイズは15ブロック。
改善前(ms) | 改善後(ms) | 改善後/改善前 | |
---|---|---|---|
平均 | 39533 | 5384 | 13.6% |
中央値 | 39422 | 5378 | 13.6% |
最大値 | 40597 | 5430 | 13.4% |
最小値 | 39287 | 5363 | 13.7% |
平均で、処理時間が13.6%になった。
NPS比較
floodgateの棋譜からサンプリングした100局面で、1秒探索した際のNPSを測定した。
10回測定した平均値を算出し、100局面について平均、中央値、最大値、最小値を算出した。
RTX3090、2スレッド
改善前(ms) | 改善後(ms) | 改善後/改善前 | |
---|---|---|---|
平均値 | 28862 | 29246 | 101.5% |
中央値 | 30155 | 30196 | 100.1% |
最大値 | 31660 | 31557 | 106.4% |
最小値 | 17078 | 17746 | 99.1% |
※改善後/改善前は同一局面での比
100局面の比の平均で1.5%NPSが向上した。
A100x8、GPUあたり4スレッド
改善前(ms) | 改善後(ms) | 改善後/改善前 | |
---|---|---|---|
平均値 | 295397 | 292695 | 99.1% |
中央値 | 297469 | 294976 | 99.2% |
最大値 | 343213 | 343739 | 102.3% |
最小値 | 233813 | 229470 | 94.7% |
※改善後/改善前は同一局面での比
100局面の比の平均で0.9%NPSが低下した。
入力特徴量作成処理の時間が短くなっているのに、NPSが低下する理由がわからない。
NPSはGPUの温度などで毎回ぶれるため、測定誤差と考えられる。
入力特徴量作成は、各プレイアウトで1回しか呼び出さないため、ボトルネックもなっていないため、NPSには影響がないと考えられる。
まとめ
入力特徴量作成処理に無駄な処理があっため、改善を行ったことで、処理時間が13.6%になった。
ただし、入力特徴量作成処理はボトルネックになっておらず、NPSにはほとんど影響がなかった。
学習時にも、入力特徴量作成処理を呼び出しているが、データローダの並列化を行っているため、影響はなさそうである。
追記
10ブロックのモデルでも確認した。
RTX3090、2スレッド
改善前(ms) | 改善後(ms) | 改善後/改善前 | |
---|---|---|---|
平均値 | 43589 | 46531 | 107.1% |
中央値 | 46058 | 51137 | 108.0% |
最大値 | 61426 | 57525 | 113.3% |
最小値 | 19727 | 20366 | 90.7% |
※改善後/改善前は同一局面での比
100局面の比の平均で7.1%NPSが向上した。
A100x8、GPUあたり5スレッド
改善前(ms) | 改善後(ms) | 改善後/改善前 | |
---|---|---|---|
平均値 | 306643 | 315190 | 102.8% |
中央値 | 306638 | 313558 | 102.6% |
最大値 | 419507 | 449810 | 107.2% |
最小値 | 247300 | 243651 | 97.5% |
※改善後/改善前は同一局面での比
100局面の比の平均で2.8%NPSが向上した。
2021/1/2 追記
改善後(ms)の数値が20回の平均値ではなく10回目の数値になっていたため修正した。