やねうら王miniのデバッグありが㌧

前回の記事、「やねうら王miniのデバッグきぼんぬ」において、コメント欄でたくさんのバグレポートを頂戴した。バグレポートをしてくれた全員に焼き肉をご馳走する次第である。(私を目撃したときに言ってもらえれば)

とりあえず、前回記事のコメントでもらっていたバグはすべて修正して、さきほどGitHubのほうにコミットした。V2.21である。

修正前のバージョンよりR80ぐらい上がっているはずである。

どんだけ強くなったのかなーと思って、やねうら王2015と対局させたところ、100回やって、100敗した。さすがにこの結果には禿げそうである。

評価関数のportingに失敗しているのかと思って、再度、いくつかの局面で値とか内訳とか見比べてみたが、ぴったり一致する。評価関数の全計算と差分計算が一致することは、そのASSERTに引っかからないことから間違いないと思うので、それ以外の何かで豪快にバグっているのだとしか思えない。

原因がよくわからないので、逆にやねうら王nanoをやねうら王2015のほうにportingして、やねうら王nanoと強さを比較しようかと思う。そこから同じ探索結果になっているのかなどを検証していけば早い段階で原因がつかめるに違いない。

とりあえず、このバグ(?)を修正すれば、やねうら王classicは、やねうら王2015と同じ程度の棋力になるはずである。今月中になんとかする。待っててくんろ。(その他、バグを発見した人は、この記事のコメント欄にでもどうぞ。)

やねうら王miniのデバッグありが㌧」への22件のフィードバック

  1. 評価関数が以前のものと同等になっていることを証明するには、perftの末端局面でevaluate()呼び出してその和を返すような関数書いて、その値が、やねうら王2015といまのやねうら王とで一致するかを見ればいいのか…そうか…。明後日やる。

  2. ハッシュは 2GB しか割り当ててませんが将棋所でハッシュ使用率が 1% とか 2%なんですがそんなもんなんですかね?うさぴょん2だと95%とか100%とか出ててずいぶん違う気がします。

      • tt.cpp:70
        これコメントと重みが16倍ほど違いません?
        だからといって世代優先なのは変わりませんけど。
        depth8 はONE_PLYで割られているので 1/2、generation8は4ずつ増えるから4で割らないといけないし。
        4ずつ増えるのはコメント通りと読むとしてもONE_PLY は逆では?
        (変えたら強くなった気がしますがよくわかりません。)

        • そこは、コメントのほうが間違ってますね。この部分のコード自体はStockfishのコードと等価でして。

          まあ、置換表メモリがふんだんにあればそもそもそんなに衝突しないので衝突時の戦略を調整しても普通は強さに寄与しないかと思います。

  3. 2.21はshougiGUIで複数候補手で検討すると、最善手以外は詰み表示になって探索してないような。ビルド失敗したのかな。

  4. バグとはちょっと違うんですが、自前ビルドだとやねうら王CLASSICはAperyややねうら王NANO PLUSと比較してNPSが半分程度しか出ていません。そんなもんなんでしょうか?

    シングルスレッド、corei5 2500, USE_AVX2はオフ
    clasic 462kN/sec
    nano plus 809kN/sec
    Apery 825kN/sec

    • やねうら王、AVX2なしのときは、エミュレーションコードが書いてあって、それが(Aperyと比べると)足を引っ張っているような気がします…が、それにしても遅すぎなので評価関数の差分計算まわりとか何かやらかしている予感。

      • 1手詰め判定でAVX2のコードがモリモリ呼びだされていて、AVX2なしだと、そこが足を引っ張っているような気がしてきました。まあ、そこは仕方ないかと…。

        あとは置換表にhitしたときにevaluate()呼び出さなくて、これが評価関数の差分計算を阻害しているので、この点は修正しました。(V2.24)

  5. バグではないのですが、高速化できそうなところがありました。Positionクラスのeval_list()関数は、戻り値はEval::EvalList型ではなくEval::EvalList*型を返すようにしてはどうでしょうか?
    プロファイラで見る限り、eval_list()が時間がかかっているようなので、Eval::EvalList*型を返すように変更したところ、npsが15%ほど上がりました。コピー渡しになっているのが重い原因かと思われます。

  6. 質問です。

    mate1ply.cppに以下のコードが2箇所あります。

    auto cut_dirs = cutoff_directions(to_direct, long_effect.directions_of(Us, to) & (Effect8::Directions)~LongE
    ffect::long_effect16_of(type_of(pc)));

    このときに一番後ろでtype_of(pc)になっていますが、long_effect16_ofの引数は色付きなので、これはpcでよいような気がしますがどうでしょうか?

        • 横から失礼します。
          修正前後のプログラムを「test rp」で比べてみましたところ、0.07%ほど詰み発見率が下がってしまったようです。

          long_effect16_of()の引数はpcが正しいと思いますが、
          その後(Effect8::Directions)にcastする前に、後手の場合は8桁右シフトする必要があるように思いますが、いかがでしょうか?
          これを試してみたところ、「test rp」で元のlong_effect16_of(type_of(pc))に比べて0.006%ほど詰み発見率が上がりました。

          後手で8桁右シフトせずに(Effect8::Directions)にcastすると、長い利きがないように見えてしまうようです。(下図参照)
          飛・角・龍・馬の長い利きは先後で変わらないので、type_of(pc)の方が詰み発見率が高かったのですが、
          香の長い利きは先後で異なるので、この分が0.006%にあたるようです。

          << —– 先手の飛車
          << pc= 飛
          << LongEffect::long_effect16_of(pc)=90
          << ~LongEffect::long_effect16_of(pc)=-91
          << (Effect8::Directions)LongEffect::long_effect16_of(pc)=
          << .*.
          << *+*
          << .*.
          <<
          << (Effect8::Directions)~LongEffect::long_effect16_of(pc)=
          << *.*
          << .+.
          << *.*
          <<
          << (Effect8::Directions)(LongEffect::long_effect16_of(pc) >> 8)=
          << …
          << .+.
          << …
          <<
          << (Effect8::Directions)(~LongEffect::long_effect16_of(pc) >> 8)=
          << ***
          << *+*
          << ***
          <<
          << —– 後手の飛車
          << pc=^飛
          << LongEffect::long_effect16_of(pc)=23040
          << ~LongEffect::long_effect16_of(pc)=-23041
          << (Effect8::Directions)LongEffect::long_effect16_of(pc)=
          << …
          << .+.
          << …
          <<
          << (Effect8::Directions)~LongEffect::long_effect16_of(pc)=
          << ***
          << *+*
          << ***
          <<
          << (Effect8::Directions)(LongEffect::long_effect16_of(pc) >> 8)=
          << .*.
          << *+*
          << .*.
          <<
          << (Effect8::Directions)(~LongEffect::long_effect16_of(pc) >> 8)=
          << *.*
          << .+.
          << *.*
          <<
          << —–

  7. see()で少しだけ高速化できそうな箇所がありましたのでご報告します。
    attackers_to()が重いようなので、see()の冒頭でattackers_to()を呼ぶ前にeffected_to()を呼び、
    相手駒の利きがなければすぐにreturnしてみましたところ、npsが4%ほど向上しました。(AVX2なしのPCで測定)

    Long Effect Libraryの利きの更新で遅くなった分を回収できるかと期待したのですが、
    思ったほど速くはなりませんでした。相手駒の利きがある場合はかえって遅くなりますし…

    ※see()の「駒打ち」「成り」「その他」の3か所のattackers_to()のうち、
    「成り」と「その他」では引数3つのeffected_to(Color c, Square sq, Square kingSq)を使いました。
    3つ目の引数はkingSqですが、玉でなくても大丈夫そうでしたので。

    • ちょっと考えてみたのですが、駒打ちでない場合、fromからtoに駒を動かすのでfromの位置に敵の遠方駒の利きが「fromからto」の方向にあると、toに動かしたあと、この遠方駒によって取られてしまうので、私がやってみたところ、2,3%しか高速化しなかったです。もうちょっと考えてみます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です