Quiescence Search doesn't improve strength

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
lithander
Posts: 880
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Quiescence Search doesn't improve strength

Post by lithander »

hgm wrote: Mon Mar 01, 2021 9:10 pm I think you worry too much about a little slowdown for a given search depth. You have tested yourself that QS + PST gave you about 500 Elo at fixed depth. It would take an enormous slowdown to take that 500 Elo back. (Like a factor 100 or so.)
Sven wrote: Mon Mar 01, 2021 8:38 pm I get the impression that you might draw conclusions too early. Have you really sorted out that there is no bug involved? The most common reason for unusual behaviour is a bug ... That might be everywhere: in your search, move ordering, evaluation, especially PST implementation (since that one is new), ... A very common mistake (and one that I made often enough myself!) is to believe that something works as it should, without testing it.
That sum's my internal dialogue up perfectly. :lol: Do I worry too much or is there a bug? That the most common reason for unusual behavior is a bug is an experience I've made countless times, too. But as I am new to chess programming it's never clear when I'm observing unusual behaviour or when I'm just having the wrong expectations.

And it's a bit hard to form the right expectations because it's just too easy to unintentionally compare apples with oranges.

For example Mike's results are really impressive. 13 plys with the same feature set?? Only that it's not really the same...
Mike Sherwin wrote: Mon Mar 01, 2021 9:03 pm I do have one move ordering trick that I use in my engines that others do not use. And the idea has been extended in Bric but I don't know if the extension is any good.
Not meaning to complain btw... you guys and this thread in particular have been super helpful. For now I will suspend my worries and try to get within spitting distance of Rustic Alpha with the features I have through optimizations and choosing the right PSTs. If I get there I'll call it version 0.3 otherwise I'm starting a serious bug-hunt! ;)

And after that it seems I'll have to have a look into move ordering beyond the use of MVV-LVA. The "internal iterative deeping" idea sounds really clever and if I understood Mike correctly he's doing something similar?
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Quiescence Search doesn't improve strength

Post by mvanthoor »

lithander wrote: Mon Mar 01, 2021 10:48 pm And after that it seems I'll have to have a look into move ordering beyond the use of MVV-LVA. The "internal iterative deeping" idea sounds really clever and if I understood Mike correctly he's doing something similar?
Everybody is doing something similar :lol:

Basically, you can use the results such as the PV/best move of the previous iteration to re-order your moves in the root (where the number of plies made is 0); contrary to what I believed, it seems that this is still a good idea even if you have a hash table. On top of that, you can order the move you get from the hash table (if any), and do killer and history ordering. All ordering (except for maybe the hash move ordering) will be for Alpha 3 in my case.

Good luck with your optimizations. As PST + QSearch gave you 500+ Elo, you should at least be very close to Rustic Alpha 1 now.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
Uri Blass
Posts: 10267
Joined: Thu Mar 09, 2006 12:37 am
Location: Tel-Aviv Israel

Re: Quiescence Search doesn't improve strength

Post by Uri Blass »

Mike Sherwin wrote: Mon Mar 01, 2021 9:03 pm
lithander wrote: Mon Mar 01, 2021 8:07 pm
Mike Sherwin wrote: Mon Mar 01, 2021 3:54 amWhen I wrote RomiChess the eval was done before Qsearch was written. It ran slow. The node rate was great but it did not search very deep at all. As soon as I added Qsearch it was the deepest single thread searcher that I knew of at the time of its release. Even today it is not bad.
But when you finally added QSearch you probably had not only a pretty good evaluation function but also several sophsticated pruning methods in addition to just alpha beta, right?

I have nothing but MVV-LVA implemented at this point which doesn't profit of a good the evaluation. Or should it? Could it...? I guess you could score a move by the difference in the static evaluation it causes instead of the MVV-LVA value of the captured peaces. It's worth a try.

You example of how a changing PV hurts the pruning of the following iteration requires something like history heuristics or a hashtable or something comparable? Otherwise, how would the previous PV be relevant to the AB search?

By now I think that I was a little early to implement QSearch and that it really unleashes it's power in combination with a very good eval *and* pruning techniques that rely on a very good evaluation and getting rid of the horizon effect. And I don't have these, yet.

For Context:

The current development branch of my engine is basically having the same features as Rustic Alpha. For the upcoming release of version 0.3 I'm trying to implement all features suggested by Marcel and Sven in this thread as essential for a minimal engine.
  • Board representation
  • Move generator
  • Alpha/Beta search
  • QSearch
  • MVV-LVA move sorting
  • Evaluation: Material count and PST
  • Iterative deepening
  • Time controls
The bold things are features that were not present in version 0.2 (1050 ELO on CCRL) and that I'm adding right now. I'm just trying to make sure there are no bugs hidden in there but for that assessment I need to form realistic expectations of what these features, correctly implemented, should do for me. I think I can expect 1500-1600 ELO? (My move generator is very simple and thus a little slow) But the depths RomiChess64P3n achieves on the startpositon are waaaaaay out of reach in my situation, I think. With the above listed features 7-8 plys in one secodn of computation is a more realistic goal, or what would you say?
Every Engine has its own peculiarities. Just the order in which moves are generated can affect all sorts of things. I don't know if you are aware but I have an engine at about the same point of development as yours and Marcel's called Bricabrac. Let's compare list.
  • Board representation - yes
  • Move generator - yes
  • Alpha/Beta search -yes
  • QSearch - yes
  • MVV-LVA move sorting - yes
  • Evaluation: Material count and PST - yes
  • Iterative deepening - yes
  • Time controls
- yes (just seconds per move so far)

Bric also has of yesterday Principle variation search using the pvs[ply][ply] and pvl[ply] triangular array. This allows the principle variation to be searched and displayed. That is the extent of Bric right now. Just added the zero window part of pvs search. Running test against TSCP1.81 is so far Bric +2, -1, =5. Some of those draws are 3 fold reps in which Bric had a winning position. Three fold rep is next on the todo list. Then Bric will be a minimal engine in my opinion.

So you are aiming for 7 to 8 ply. In how long? I'll do a 60 second search of the original position with Bric as it is now, brb. Okay back.
FEN: rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1

Bricabrac:
1 00:00 20 20 +0.36 g1f3
2 00:00 87 87 0.00 g1f3 g8f6
3 00:00 701 701 +0.35 g1f3 g8f6 d2d4
4 00:00 4k 4k 0.00 g1f3 g8f6 d2d4 d7d5
5 00:00 9k 9k +0.33 g1f3 g8f6 d2d4 d7d5 b1c3
6 00:00 16k 16k 0.00 g1f3 g8f6 d2d4 d7d5 b1c3 b8c6
7 00:00 59k 59k +0.28 g1f3 g8f6 d2d4 d7d5 b1c3 b8c6 c1e3
8 00:00 197k 19,684k 0.00 g1f3 g8f6 d2d4 d7d5 b1c3 b8c6 c1e3 c8e6
9 00:00 3,094k 9,981k +0.29 e2e4 b8c6 g1f3 g8f6 b1c3 d7d5 e4d5 f6d5 d2d4
10 00:00 5,071k 10,142k +0.09 e2e4 e7e5 g1f3 b8c6 b1c3 g8f6 d2d4 e5d4 f3d4 f8d6
11 00:14 144,410k 9,959k +0.23 d2d4 d7d5 g1f3 b8c6 b1c3 g8f6 c1f4 c8e6 e2e3 f6e4 f1d3
12 00:19 204,401k 10,707k +0.02 d2d4 d7d5 g1f3 g8f6 b1c3 c8f5 c1e3 b8c6 f3e5 f6e4 c3e4 c6e5
13 00:35 365,906k 10,244k +0.25 d2d4 d7d5 g1f3 b8c6 b1c3 c8f5 c1f4 g8f6 e2e3 f6e4 c3e4 f5e4 f1d3

Ply 14 was started but did not finish in time. So bric finished ply 13 in 35 seconds. PVS adds a ply or maybe more. Maybe someone else can tell us how much. Anyway, your 7 to 8 ply expectation is very conservative. I do have one move ordering trick that I use in my engines that others do not use. And the idea has been extended in Bric but I don't know if the extension is any good. So to answer the question, I think that you can raise your expectations just a bit. 8-)

Bric's search function.

Code: Select all

s32 Search(Thread* t, Move* m, s32 alpha, s32 beta, s32 depth) {
  s32 mi, j, count = 0, goodness = 0;
  u64 n;
  Move* mov;

  if (depth == 0) return Qsearch(t, m, alpha, beta);

  if (clock() > end) return alpha;

  pvl[ply] = ply;

  n = GenMoves(t, m);

  if (!n) return -ILLEGAL;

 if (depth > 3) {
    for (mi = 0; mi < n; mi++) {
      mov = m + mi;
      MakeMove(t, mov);
      mov->score = -Qsearch(t, m + n, -beta - 1000, -alpha + 100);
      TakeBack(t, mov);
      goodness += (move->score >= beta);
    }
  }

 if (fpv) {
   for (mi = 0; mi < n; mi++) {
     mov = m + mi;
     if (mov->fs == pvs[0][ply].fs && mov->ts == pvs[0][ply].ts && mov->type == pvs[0][ply].type) {
       mov->score = 1000000;
       break;
     }
   }
  }

  for (mi = 0; mi < n; mi++) {
    Sort(t, m, mi, n);
    mov = m + mi;
    MakeMove(t, mov);
    if (goodness && mov->score >= beta) {
      mov->score = -Search(t, m + n, -beta, -beta + 1, depth - 3);
      if (mov->score < beta)
        mov->score = -Search(t, m + n, -beta, -alpha, depth - 1);
    }
    else {
      if (fpv) {
        mov->score = -Search(t, m + n, -alpha - 1, -alpha, depth - 1);
        if (m->score > alpha) mov->score = -Search(t, m + n, -beta, -alpha, depth - 1);
      }
      else {
        mov->score = -Search(t, m + n, -beta, -alpha, depth - 1);
      }
    }
    TakeBack(t, mov);
    fpv = false;
    if (mov->score == ILLEGAL) continue;
    count++;
    if (mov->score > alpha) {
      if (mov->score >= beta) return beta;
      alpha = mov->score;
      pvs[ply][ply] = *mov;
      for (j = ply + 1; j < pvl[ply + 1]; j++) pvs[ply][j] = pvs[ply + 1][j];
      pvl[ply] = pvl[ply + 1];
    }
  }
  if (!count) {
    if (InCheck[wtm](t, king[wtm])) {
      return -(MATE - ply);
    }
    else return STALEMATE;
  }
  return alpha;
}

I see that you get 10M nodes per second.
I am surprised by this high number and I wonder what is the hardware that you use and if you use more than one cpu core in the search.

I do not know about engines that search this number of nodes with one cpu core and I think that using more than one core is not something that you do in the first steps of building an engine.
User avatar
emadsen
Posts: 434
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Quiescence Search doesn't improve strength

Post by emadsen »

Sven wrote: Mon Mar 01, 2021 8:38 pm I get the impression that you might draw conclusions too early. Have you really sorted out that there is no bug involved? The most common reason for unusual behaviour is a bug ... That might be everywhere: in your search, move ordering, evaluation, ...
I agree with Sven. I think you have a bug, likely many bugs. Quiescence search should greatly improve the strength of your engine.

I recommend you spend time adding Debug.Asserts to verify what you believe is true actually is true. Does your evaluation function return the same magnitude of score, but different sign, if you evaluate a position where white is to move and the same position except black to move (assuming you give no bonus for initiative)? Does your evaluation function return the same score before and after a search command- meaning scores don't accumulate due to an eval term that's not reset? Is your search function examining all moves? Moves searched can vary from moves counted in perft if the two functions don't share the same call stack. If you've implemented search horizon reductions or futility pruning, does the engine reduce or skip obviously bad moves, or does it reduce or skip critical moves like checks and captures?

Quiescence search is so fundamental to a chess engine I didn't bother measuring its impact on playing strength as I documented my progress developing MadChess 2.0 and 3.0. For each version, I started with a baseline engine that included quiescence search. Why? Because without it the engine will evaluate many positions as winning or equal even though it's leaving a valuable piece hanging. This concern is orthogonal to- is unrelated to- piece square tables. Piece placement is insignificant compared to the loss of a queen, rook, or minor piece.

Enough hand waving. I decided to measure the value of quiescence search in my engine by removing it. Well, I didn't remove it. I simplified it by eliminating any searching and reducing it to the following lines:

Code: Select all

private int GetQuietScore(Board Board)
{
    // All searching and stand-pat logic removed.    
    var drawnEndgame = _evaluation.IsDrawnEndgame(Board.CurrentPosition);
    Board.CurrentPosition.StaticScore = Board.PreviousPosition?.PlayedMove == Move.Null
        ? -Board.PreviousPosition.StaticScore
        : drawnEndgame ? 0 : _evaluation.GetStaticScore(Board.CurrentPosition);
    return Board.CurrentPosition.StaticScore;
}
After 435 games at 2m+1s time control, MadChess performs 232 +/- 27 Elo weaker.
My C# chess engine: https://www.madchess.net
Mike Sherwin
Posts: 860
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Quiescence Search doesn't improve strength

Post by Mike Sherwin »

Uri Blass wrote: Tue Mar 02, 2021 5:18 am
Mike Sherwin wrote: Mon Mar 01, 2021 9:03 pm
lithander wrote: Mon Mar 01, 2021 8:07 pm
Mike Sherwin wrote: Mon Mar 01, 2021 3:54 amWhen I wrote RomiChess the eval was done before Qsearch was written. It ran slow. The node rate was great but it did not search very deep at all. As soon as I added Qsearch it was the deepest single thread searcher that I knew of at the time of its release. Even today it is not bad.
But when you finally added QSearch you probably had not only a pretty good evaluation function but also several sophsticated pruning methods in addition to just alpha beta, right?

I have nothing but MVV-LVA implemented at this point which doesn't profit of a good the evaluation. Or should it? Could it...? I guess you could score a move by the difference in the static evaluation it causes instead of the MVV-LVA value of the captured peaces. It's worth a try.

You example of how a changing PV hurts the pruning of the following iteration requires something like history heuristics or a hashtable or something comparable? Otherwise, how would the previous PV be relevant to the AB search?

By now I think that I was a little early to implement QSearch and that it really unleashes it's power in combination with a very good eval *and* pruning techniques that rely on a very good evaluation and getting rid of the horizon effect. And I don't have these, yet.

For Context:

The current development branch of my engine is basically having the same features as Rustic Alpha. For the upcoming release of version 0.3 I'm trying to implement all features suggested by Marcel and Sven in this thread as essential for a minimal engine.
  • Board representation
  • Move generator
  • Alpha/Beta search
  • QSearch
  • MVV-LVA move sorting
  • Evaluation: Material count and PST
  • Iterative deepening
  • Time controls
The bold things are features that were not present in version 0.2 (1050 ELO on CCRL) and that I'm adding right now. I'm just trying to make sure there are no bugs hidden in there but for that assessment I need to form realistic expectations of what these features, correctly implemented, should do for me. I think I can expect 1500-1600 ELO? (My move generator is very simple and thus a little slow) But the depths RomiChess64P3n achieves on the startpositon are waaaaaay out of reach in my situation, I think. With the above listed features 7-8 plys in one secodn of computation is a more realistic goal, or what would you say?
Every Engine has its own peculiarities. Just the order in which moves are generated can affect all sorts of things. I don't know if you are aware but I have an engine at about the same point of development as yours and Marcel's called Bricabrac. Let's compare list.
  • Board representation - yes
  • Move generator - yes
  • Alpha/Beta search -yes
  • QSearch - yes
  • MVV-LVA move sorting - yes
  • Evaluation: Material count and PST - yes
  • Iterative deepening - yes
  • Time controls
- yes (just seconds per move so far)

Bric also has of yesterday Principle variation search using the pvs[ply][ply] and pvl[ply] triangular array. This allows the principle variation to be searched and displayed. That is the extent of Bric right now. Just added the zero window part of pvs search. Running test against TSCP1.81 is so far Bric +2, -1, =5. Some of those draws are 3 fold reps in which Bric had a winning position. Three fold rep is next on the todo list. Then Bric will be a minimal engine in my opinion.

So you are aiming for 7 to 8 ply. In how long? I'll do a 60 second search of the original position with Bric as it is now, brb. Okay back.
FEN: rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1

Bricabrac:
1 00:00 20 20 +0.36 g1f3
2 00:00 87 87 0.00 g1f3 g8f6
3 00:00 701 701 +0.35 g1f3 g8f6 d2d4
4 00:00 4k 4k 0.00 g1f3 g8f6 d2d4 d7d5
5 00:00 9k 9k +0.33 g1f3 g8f6 d2d4 d7d5 b1c3
6 00:00 16k 16k 0.00 g1f3 g8f6 d2d4 d7d5 b1c3 b8c6
7 00:00 59k 59k +0.28 g1f3 g8f6 d2d4 d7d5 b1c3 b8c6 c1e3
8 00:00 197k 19,684k 0.00 g1f3 g8f6 d2d4 d7d5 b1c3 b8c6 c1e3 c8e6
9 00:00 3,094k 9,981k +0.29 e2e4 b8c6 g1f3 g8f6 b1c3 d7d5 e4d5 f6d5 d2d4
10 00:00 5,071k 10,142k +0.09 e2e4 e7e5 g1f3 b8c6 b1c3 g8f6 d2d4 e5d4 f3d4 f8d6
11 00:14 144,410k 9,959k +0.23 d2d4 d7d5 g1f3 b8c6 b1c3 g8f6 c1f4 c8e6 e2e3 f6e4 f1d3
12 00:19 204,401k 10,707k +0.02 d2d4 d7d5 g1f3 g8f6 b1c3 c8f5 c1e3 b8c6 f3e5 f6e4 c3e4 c6e5
13 00:35 365,906k 10,244k +0.25 d2d4 d7d5 g1f3 b8c6 b1c3 c8f5 c1f4 g8f6 e2e3 f6e4 c3e4 f5e4 f1d3

Ply 14 was started but did not finish in time. So bric finished ply 13 in 35 seconds. PVS adds a ply or maybe more. Maybe someone else can tell us how much. Anyway, your 7 to 8 ply expectation is very conservative. I do have one move ordering trick that I use in my engines that others do not use. And the idea has been extended in Bric but I don't know if the extension is any good. So to answer the question, I think that you can raise your expectations just a bit. 8-)

Bric's search function.

Code: Select all

s32 Search(Thread* t, Move* m, s32 alpha, s32 beta, s32 depth) {
  s32 mi, j, count = 0, goodness = 0;
  u64 n;
  Move* mov;

  if (depth == 0) return Qsearch(t, m, alpha, beta);

  if (clock() > end) return alpha;

  pvl[ply] = ply;

  n = GenMoves(t, m);

  if (!n) return -ILLEGAL;

 if (depth > 3) {
    for (mi = 0; mi < n; mi++) {
      mov = m + mi;
      MakeMove(t, mov);
      mov->score = -Qsearch(t, m + n, -beta - 1000, -alpha + 100);
      TakeBack(t, mov);
      goodness += (move->score >= beta);
    }
  }

 if (fpv) {
   for (mi = 0; mi < n; mi++) {
     mov = m + mi;
     if (mov->fs == pvs[0][ply].fs && mov->ts == pvs[0][ply].ts && mov->type == pvs[0][ply].type) {
       mov->score = 1000000;
       break;
     }
   }
  }

  for (mi = 0; mi < n; mi++) {
    Sort(t, m, mi, n);
    mov = m + mi;
    MakeMove(t, mov);
    if (goodness && mov->score >= beta) {
      mov->score = -Search(t, m + n, -beta, -beta + 1, depth - 3);
      if (mov->score < beta)
        mov->score = -Search(t, m + n, -beta, -alpha, depth - 1);
    }
    else {
      if (fpv) {
        mov->score = -Search(t, m + n, -alpha - 1, -alpha, depth - 1);
        if (m->score > alpha) mov->score = -Search(t, m + n, -beta, -alpha, depth - 1);
      }
      else {
        mov->score = -Search(t, m + n, -beta, -alpha, depth - 1);
      }
    }
    TakeBack(t, mov);
    fpv = false;
    if (mov->score == ILLEGAL) continue;
    count++;
    if (mov->score > alpha) {
      if (mov->score >= beta) return beta;
      alpha = mov->score;
      pvs[ply][ply] = *mov;
      for (j = ply + 1; j < pvl[ply + 1]; j++) pvs[ply][j] = pvs[ply + 1][j];
      pvl[ply] = pvl[ply + 1];
    }
  }
  if (!count) {
    if (InCheck[wtm](t, king[wtm])) {
      return -(MATE - ply);
    }
    else return STALEMATE;
  }
  return alpha;
}

I see that you get 10M nodes per second.
I am surprised by this high number and I wonder what is the hardware that you use and if you use more than one cpu core in the search.

I do not know about engines that search this number of nodes with one cpu core and I think that using more than one core is not something that you do in the first steps of building an engine.
3950x 4.2 GHz single thread. Set up for multithreaded but not implemented yet.
Source code posted here. http://talkchess.com/forum3/viewtopic.p ... 51#p885504
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Quiescence Search doesn't improve strength

Post by Sven »

emadsen wrote: Tue Mar 02, 2021 5:35 am
Sven wrote: Mon Mar 01, 2021 8:38 pm I get the impression that you might draw conclusions too early. Have you really sorted out that there is no bug involved? The most common reason for unusual behaviour is a bug ... That might be everywhere: in your search, move ordering, evaluation, ...
I agree with Sven. I think you have a bug, likely many bugs. Quiescence search should greatly improve the strength of your engine.

I recommend you spend time adding Debug.Asserts to verify what you believe is true actually is true. [...]
Despite the title of this thread, the current problem reported by the OP is no longer quiescence search (which now appears to deliver expected results) but a substantial increase of tree size after adding a PST to the former material-only eval. On everything else you wrote about possible sources of bugs I fully agree, of course.
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
User avatar
hgm
Posts: 27788
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Quiescence Search doesn't improve strength

Post by hgm »

But that is not a 'problem'. It is a well known that material-only eval enormously speeds up the search. Vincent Diepeveen already pointed that out to me in 2008. And it is of course obvious that it should: virtually every move in a cut-node will be a cut-move, because its score will equal beta. With a more distinguishing evaluation, even a whole bunch of advanced move-ordering tricks will not be able to approach that success rate. Let alone when you compare it with an engine that hardly does any move ordering (no killer, no history, no IID).
User avatar
emadsen
Posts: 434
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Quiescence Search doesn't improve strength

Post by emadsen »

Sven wrote: Tue Mar 02, 2021 8:06 am Despite the title of this thread, the current problem reported by the OP is no longer quiescence search (which now appears to deliver expected results) but a substantial increase of tree size after adding a PST to the former material-only eval.
Ah, OK.
hgm wrote: Tue Mar 02, 2021 9:05 am But that is not a 'problem'. It is a well known that material-only eval enormously speeds up the search. Vincent Diepeveen already pointed that out to me in 2008. And it is of course obvious that it should: virtually every move in a cut-node will be a cut-move, because its score will equal beta.
Right. OP, how are you defining "problem"? I hope you're defining "problem" as a code change that weakens engine strength when measured with a statistically significant number of timed games versus other engines of approximately equal rating (+/- 100 Elo). If not, then your perception of a "problem" may not align with how well the engine plays chess in timed games, for the reason stated above by H.G.
My C# chess engine: https://www.madchess.net
User avatar
lithander
Posts: 880
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Quiescence Search doesn't improve strength

Post by lithander »

emadsen wrote: Tue Mar 02, 2021 2:01 pm OP, how are you defining "problem"? I hope you're defining "problem" as a code change that weakens engine strength when measured with a statistically significant number of timed games versus other engines of approximately equal rating (+/- 100 Elo). If not, then your perception of a "problem" may not align with how well the engine plays chess in timed games, for the reason stated above by H.G.
My definition of a problem is when after a code change the program does not behave as expected. In that case either there is a bug in the code or my expectations are wrong. Both would be a problem.

Because not everyone will read the full thread and be mislead by the title and my original post (which I can't edit) to think that no progress has been made and that the only explaination for seeing no strength gain after adding QSearch is the presence of one or many bugs in my code here's a little recap of the threads content.

1.) I implemented QSearch and MVV-LVA move ordering and didn't measure a significant ELO improvement in my test. I assumed a bug but couldn't find one. So I came here looking for advice.

2.) The first great tip was to not use time controls but measure to a fixed depth. Now I saw a small improvement in strength which was negated by my inefficient (e.g. use of LINQ) implementation under time (and not depth) constraints.

3.) Why was it only small? Shouldn't you expect much larger gains? Marcel disabled PSTs in Rustics evaluation and was now only counting material like MinimalChess and found that now the QSearch didn't provide him much benefit either.
This was the 2nd learning: You need a better eval for QSearch to provide a significant boost to playing strength. My expectations were wrong. So I implemented PSTs.

4.) In the meantime Mike Shervin predicted that the "better the evaluation function is the better the pruning will be and the deeper the search will search in a given time". So with PSTs in place I measured that and found the opposite to be true. The pruning became worse. I worried a little about that, fearing that it might mean that there are bugs somewhere, after all. Intuitively it seemed to make sense though and H.G. confirmed my expectations on this one: material-only evaluation provides more opportunity for pruning and is hard to beat with better evals *unless* you also add some smarter move ordering techniques beyond MVV-LVA that actually make use of the better evaluation to play the most promising moves first. But I don't have any of these techniques implemented, yet.

This provided the 3rd learning: Move ordering is a topic I need to spend more time on after this. Mike showed his search routine and the amazing search depth of his current engine that reaches these depths without using a hash table. H.G. also introduced the idea of "internal iterative deepening".

So for me the thread has been very informative. What I didn't find so far was bugs. Even though the title may sound like that's the most likely explanation for when QSearch doesn't improve strength it turns out in my my specific context there were other reasons. The consensus seems to be that Rustic Alpha's performance is a good benchmark to what you can achieve with the specific feature set we both have implemented in our engines at this point. I have still the aforementioned inefficiencies to take care of but even at this point the addition of MVV-LVA move ordering, QSearch and PSTs combined finally provided a boost of ~500 ELO over the previous version of MinimalChess. So the thread title doesn't really describe the situation accurately anymore. And there's no pressing reason to start looking for bugs at this point, I think. (which doesn't mean there aren't any, just that they seem to not hurt the performance too much at this point)
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Quiescence Search doesn't improve strength

Post by mvanthoor »

lithander wrote: Tue Mar 02, 2021 4:59 pm This provided the 3rd learning: Move ordering is a topic I need to spend more time on after this. Mike showed his search routine and the amazing search depth of his current engine that reaches these depths without using a hash table. H.G. also introduced the idea of "internal iterative deepening".
Yeah... that hurt. I had expected more from the hash table, even though it's not finished yet. I wonder what the end result WITH hash move sorting + MVV-LVA is going to be. I'm sure to also test PV Move sorting separately, as well as killer+history sorting. That will be for Alpha 3 though. It seems move sorting is going to be the be-all-end-all, next to raw speed, for engines that don't implement advanced pruning methods (yet).

Then, for Rustic 4 (no Alpha, because I consider the "Alpha" stage over), I hope to implement Texel Tuning merge XBoard, without adding new sorting, searching, or evaluation features. At that point the engine can connect to virtually every user interface that is currently in use, and Texel Tuning will (hopefully) squeeze the last few Elo points out of an engine that can be considered to only have all the basic features. After that, I'll see; probably add features one by one (very slowly...).

Lithander and all participants; it certainly has been an educative thread. It's a pity that, when writing my tutorial/book (an effort to gather all the knowledge in one piece of writing that actually amounts to an engine in the end), I can't credit all the people and threads I learned something from. I'd go broke hosting the 50 gigabyte of text with the references.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL