Incremental or non-incremental PST evaluation calcs

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Incremental or non-incremental PST evaluation calcs

Post by bob »

Sven Schüle wrote:
bob wrote:
hgm wrote:
bob wrote:With todays programs, the trees are deep and narrow. Which is exactly the circumstance that doesn't favor incremental calculations. The old-style wide/bushy trees were quite good for incremental update, but I don't think it is nearly as clear an advantage today...

Worth actually quantifying at some point...
They are not that narrow: a branching ratio of 2 only doubles the incremental work per end leaf from 1 to 2 updates, as all earlier updates are shared between 2, 4, 8... nodes. And that is only infinitesimally smaller than double for a tree that is not infinitely deep, so depth has certainly nothing to do with it.

Only when the EBF smaller than 1.125 would you get substantial difference in the workload of incremental updating in an 8 ply tree,
Right. But I am talking about 24-30 ply searches in typical CCT-like tournaments...
The point of HGM was like this:

- You are at a node X where you call eval() (which is somewhere within QS most of the time). The effort for the incremental PST score update done during the previous move (the move made from a node 1 ply above X) counts by 100% for that eval call.

- Assuming EBF=2 (actually it is smaller in QS which makes HGM's point somewhat weaker), the PST update during the next-to-previous move (2 plies above X) only counts by 50% for eval() at X since on average that update is shared with a sibling of X.

- The PST update done 3 plies above X counts by 25%, and so on.

In total this is ~200% for EBF=2 (300% for EBF=1.5, 500% for EBF=1.25, is that possible within the area of only QS and "low depth" nodes?) and independent from the actual path length from root to X due to "sharing".

Sven
Here is what my conjecture is based on. In a "wide search" you do an incremental update, and at the leaves, you use that value a bunch of times, as you successively make each move and then do a static eval (assuming no captures for simplicity). In a deeper/narrower search, you use that last incremental value fewer times. But you are also searching deeper. So you did more of 'em to get to that final eval call.

Whether this is significant or not, I don't know. Another idea is the normal "procrastination" idea, where you want to wait until the last minute to do something, because you might not have to do it at all, due to pruning...

I did a lot of that stuff in Cray Blitz. And I did it a lot in early Crafty versions, but it is much cleaner to have the eval stuff in Evaluate() rather than scattered around in Make/Unmake and such... The only thing I do incrementally is material, because I use that inside the search tree to make decisions.
User avatar
Don
Posts: 5106
Joined: Tue Apr 29, 2008 4:27 pm

Re: Incremental or non-incremental PST evaluation calcs

Post by Don »

bob wrote:
Sven Schüle wrote:
bob wrote:
hgm wrote:
bob wrote:With todays programs, the trees are deep and narrow. Which is exactly the circumstance that doesn't favor incremental calculations. The old-style wide/bushy trees were quite good for incremental update, but I don't think it is nearly as clear an advantage today...

Worth actually quantifying at some point...
They are not that narrow: a branching ratio of 2 only doubles the incremental work per end leaf from 1 to 2 updates, as all earlier updates are shared between 2, 4, 8... nodes. And that is only infinitesimally smaller than double for a tree that is not infinitely deep, so depth has certainly nothing to do with it.

Only when the EBF smaller than 1.125 would you get substantial difference in the workload of incremental updating in an 8 ply tree,
Right. But I am talking about 24-30 ply searches in typical CCT-like tournaments...
The point of HGM was like this:

- You are at a node X where you call eval() (which is somewhere within QS most of the time). The effort for the incremental PST score update done during the previous move (the move made from a node 1 ply above X) counts by 100% for that eval call.

- Assuming EBF=2 (actually it is smaller in QS which makes HGM's point somewhat weaker), the PST update during the next-to-previous move (2 plies above X) only counts by 50% for eval() at X since on average that update is shared with a sibling of X.

- The PST update done 3 plies above X counts by 25%, and so on.

In total this is ~200% for EBF=2 (300% for EBF=1.5, 500% for EBF=1.25, is that possible within the area of only QS and "low depth" nodes?) and independent from the actual path length from root to X due to "sharing".

Sven
Here is what my conjecture is based on. In a "wide search" you do an incremental update, and at the leaves, you use that value a bunch of times, as you successively make each move and then do a static eval (assuming no captures for simplicity). In a deeper/narrower search, you use that last incremental value fewer times. But you are also searching deeper. So you did more of 'em to get to that final eval call.

Whether this is significant or not, I don't know. Another idea is the normal "procrastination" idea, where you want to wait until the last minute to do something, because you might not have to do it at all, due to pruning...

I did a lot of that stuff in Cray Blitz. And I did it a lot in early Crafty versions, but it is much cleaner to have the eval stuff in Evaluate() rather than scattered around in Make/Unmake and such... The only thing I do incrementally is material, because I use that inside the search tree to make decisions.
Komodo's evaluation is nearly completely isolated. I could basically plug in a different eval.c module and have a completely different evaluation. The only thing that breaks this nice separation is the piece square tables but even those are populated by an initialization routine inside eval.c

Do the incrementally updated piece square tables buy us anything other that some tiny bit of speed? The intent was that it would be quite nice to always have a score estimate ready even if quite crude, but the reality is that we have found no good use for it.
jwes
Posts: 778
Joined: Sat Jul 01, 2006 7:11 am

Re: Incremental or non-incremental PST evaluation calcs

Post by jwes »

bob wrote:
Sven Schüle wrote:
bob wrote:
hgm wrote:
bob wrote:With todays programs, the trees are deep and narrow. Which is exactly the circumstance that doesn't favor incremental calculations. The old-style wide/bushy trees were quite good for incremental update, but I don't think it is nearly as clear an advantage today...

Worth actually quantifying at some point...
They are not that narrow: a branching ratio of 2 only doubles the incremental work per end leaf from 1 to 2 updates, as all earlier updates are shared between 2, 4, 8... nodes. And that is only infinitesimally smaller than double for a tree that is not infinitely deep, so depth has certainly nothing to do with it.

Only when the EBF smaller than 1.125 would you get substantial difference in the workload of incremental updating in an 8 ply tree,
Right. But I am talking about 24-30 ply searches in typical CCT-like tournaments...
The point of HGM was like this:

- You are at a node X where you call eval() (which is somewhere within QS most of the time). The effort for the incremental PST score update done during the previous move (the move made from a node 1 ply above X) counts by 100% for that eval call.

- Assuming EBF=2 (actually it is smaller in QS which makes HGM's point somewhat weaker), the PST update during the next-to-previous move (2 plies above X) only counts by 50% for eval() at X since on average that update is shared with a sibling of X.

- The PST update done 3 plies above X counts by 25%, and so on.

In total this is ~200% for EBF=2 (300% for EBF=1.5, 500% for EBF=1.25, is that possible within the area of only QS and "low depth" nodes?) and independent from the actual path length from root to X due to "sharing".

Sven
Here is what my conjecture is based on. In a "wide search" you do an incremental update, and at the leaves, you use that value a bunch of times, as you successively make each move and then do a static eval (assuming no captures for simplicity). In a deeper/narrower search, you use that last incremental value fewer times. But you are also searching deeper. So you did more of 'em to get to that final eval call.

Whether this is significant or not, I don't know. Another idea is the normal "procrastination" idea, where you want to wait until the last minute to do something, because you might not have to do it at all, due to pruning...

I did a lot of that stuff in Cray Blitz. And I did it a lot in early Crafty versions, but it is much cleaner to have the eval stuff in Evaluate() rather than scattered around in Make/Unmake and such... The only thing I do incrementally is material, because I use that inside the search tree to make decisions.
I ran a quick test where I added a counter to crafty which I incremented in makemove and reset to 0 in evaluate. I used this to calculate the average number of times makemove is called between calls to evaluate. For searches of .1 sec, it was 1.826, for searches of 30 sec, it was 2.003.
This suggests that incremental PSTs may well be faster than non-incremental.
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Incremental or non-incremental PST evaluation calcs

Post by bob »

jwes wrote:
bob wrote:
Sven Schüle wrote:
bob wrote:
hgm wrote:
bob wrote:With todays programs, the trees are deep and narrow. Which is exactly the circumstance that doesn't favor incremental calculations. The old-style wide/bushy trees were quite good for incremental update, but I don't think it is nearly as clear an advantage today...

Worth actually quantifying at some point...
They are not that narrow: a branching ratio of 2 only doubles the incremental work per end leaf from 1 to 2 updates, as all earlier updates are shared between 2, 4, 8... nodes. And that is only infinitesimally smaller than double for a tree that is not infinitely deep, so depth has certainly nothing to do with it.

Only when the EBF smaller than 1.125 would you get substantial difference in the workload of incremental updating in an 8 ply tree,
Right. But I am talking about 24-30 ply searches in typical CCT-like tournaments...
The point of HGM was like this:

- You are at a node X where you call eval() (which is somewhere within QS most of the time). The effort for the incremental PST score update done during the previous move (the move made from a node 1 ply above X) counts by 100% for that eval call.

- Assuming EBF=2 (actually it is smaller in QS which makes HGM's point somewhat weaker), the PST update during the next-to-previous move (2 plies above X) only counts by 50% for eval() at X since on average that update is shared with a sibling of X.

- The PST update done 3 plies above X counts by 25%, and so on.

In total this is ~200% for EBF=2 (300% for EBF=1.5, 500% for EBF=1.25, is that possible within the area of only QS and "low depth" nodes?) and independent from the actual path length from root to X due to "sharing".

Sven
Here is what my conjecture is based on. In a "wide search" you do an incremental update, and at the leaves, you use that value a bunch of times, as you successively make each move and then do a static eval (assuming no captures for simplicity). In a deeper/narrower search, you use that last incremental value fewer times. But you are also searching deeper. So you did more of 'em to get to that final eval call.

Whether this is significant or not, I don't know. Another idea is the normal "procrastination" idea, where you want to wait until the last minute to do something, because you might not have to do it at all, due to pruning...

I did a lot of that stuff in Cray Blitz. And I did it a lot in early Crafty versions, but it is much cleaner to have the eval stuff in Evaluate() rather than scattered around in Make/Unmake and such... The only thing I do incrementally is material, because I use that inside the search tree to make decisions.
I ran a quick test where I added a counter to crafty which I incremented in makemove and reset to 0 in evaluate. I used this to calculate the average number of times makemove is called between calls to evaluate. For searches of .1 sec, it was 1.826, for searches of 30 sec, it was 2.003.
This suggests that incremental PSTs may well be faster than non-incremental.
That's good data, although it would STILL depend on what you do incrementally. For example, Slate's incremental update to the attacks_from bitboards are murderously expensive. I found it faster to compute from scratch when they are needed. In MakeMove() you end up updating everything every time you call it. But eval takes a lot of lazy (early) exits that might not use that information. So it is still not completely clear to me that incremental will work.
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: Incremental or non-incremental PST evaluation calcs

Post by sje »

bob wrote:That's good data, although it would STILL depend on what you do incrementally. For example, Slate's incremental update to the attacks_from bitboards are murderously expensive. I found it faster to compute from scratch when they are needed.
Perhaps you meant to say attacks_to instead of attacks_from. The latter is of course required for fast move generation, while the former can be done as needed if the need is not too frequent else an incremental update is faster.
User avatar
Evert
Posts: 2929
Joined: Sat Jan 22, 2011 12:42 am
Location: NL

Re: Incremental or non-incremental PST evaluation calcs

Post by Evert »

When I added incremental attack maps to Sjaak (mainly for the purpose of more efficient attack tests) it came out as equally fast as calculating it as needed.
So I took them out again because it simplifies the code...
User avatar
hgm
Posts: 27811
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Incremental or non-incremental PST evaluation calcs

Post by hgm »

bob wrote:That's good data, although it would STILL depend on what you do incrementally. For example, Slate's incremental update to the attacks_from bitboards are murderously expensive. I found it faster to compute from scratch when they are needed. In MakeMove() you end up updating everything every time you call it. But eval takes a lot of lazy (early) exits that might not use that information. So it is still not completely clear to me that incremental will work.
Indeed, some data is more difficult to track incrementally than others. But PST (as well as material index and hash keys) are amongst the trivial ones. At least, some terms in them. Castling rights are already too complex to make incremental update of the hash keys for it worthwhile.

Attack maps are amongst the worst. The only way to make incremental update of those competitive is to take real care to postpone the update untill you are sure you need it. In Spartacus I have used attack maps for generation of captures in QS (staged in MVV order). For that you only need to update the opponent attacks in MakeMove, and they are a lot less effected by the move than your own attacks (especially if the move is a capture, which it commonly is). So you can use the partially updated map to quickly test if there are non-futile captures that are going to be searched. (You won't get a stand-pat cutoff by lazy eval, or you would not even have made the last move.) Only if there are non-futile captures, meaning you will search on, rather than take the immediate fail low), the attack map is updated for the moves of the side that did the last move.

I have currently disabled this again, because the existing code could not handle mixed slider / leaper compounds.
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Incremental or non-incremental PST evaluation calcs

Post by bob »

sje wrote:
bob wrote:That's good data, although it would STILL depend on what you do incrementally. For example, Slate's incremental update to the attacks_from bitboards are murderously expensive. I found it faster to compute from scratch when they are needed.
Perhaps you meant to say attacks_to instead of attacks_from. The latter is of course required for fast move generation, while the former can be done as needed if the need is not too frequent else an incremental update is faster.
Actually I said what I meant. :) When I wrote the first versions, I used the opposite terminology. Attacks_from was actually the attacks from the current square, not slates "where is this square attacked from?"

But you got the idea, anyway...
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: Incremental or non-incremental PST evaluation calcs

Post by sje »

atkfs = attacks from a square
atkts = attacks to a square

Code: Select all

[] dfen
FEN: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
[] dbbdb
merge     [a1 b1 c1 d1 e1 f1 g1 h1 a2 b2 c2 d2 e2 f2 g2 h2 a7 b7 c7 d7 e7 f7 g7 h7 a8 b8 c8 d8 e8 f8 g8 h8]
sweep     [a1 c1 d1 f1 h1 a8 c8 d8 f8 h8]
locbc[w]  [a1 b1 c1 d1 e1 f1 g1 h1 a2 b2 c2 d2 e2 f2 g2 h2]
locbc[b]  [a7 b7 c7 d7 e7 f7 g7 h7 a8 b8 c8 d8 e8 f8 g8 h8]
locbm[wP] [a2 b2 c2 d2 e2 f2 g2 h2]
locbm[wN] [b1 g1]
locbm[wB] [c1 f1]
locbm[wR] [a1 h1]
locbm[wQ] [d1]
locbm[wK] [e1]
locbm[bP] [a7 b7 c7 d7 e7 f7 g7 h7]
locbm[bN] [b8 g8]
locbm[bB] [c8 f8]
locbm[bR] [a8 h8]
locbm[bQ] [d8]
locbm[bK] [e8]
atkbc[w]  [b1 c1 d1 e1 f1 g1 a2 b2 c2 d2 e2 f2 g2 h2 a3 b3 c3 d3 e3 f3 g3 h3]
atkbc[b]  [a6 b6 c6 d6 e6 f6 g6 h6 a7 b7 c7 d7 e7 f7 g7 h7 b8 c8 d8 e8 f8 g8]
atkfs[a1] [b1 a2]
atkfs[b1] [d2 a3 c3]
atkfs[c1] [b2 d2]
atkfs[d1] [c1 e1 c2 d2 e2]
atkfs[e1] [d1 f1 d2 e2 f2]
atkfs[f1] [e2 g2]
atkfs[g1] [e2 f3 h3]
atkfs[h1] [g1 h2]
atkfs[a2] [b3]
atkfs[b2] [a3 c3]
atkfs[c2] [b3 d3]
atkfs[d2] [c3 e3]
atkfs[e2] [d3 f3]
atkfs[f2] [e3 g3]
atkfs[g2] [f3 h3]
atkfs[h2] [g3]
atkfs[a3] []
atkfs[b3] []
atkfs[c3] []
atkfs[d3] []
atkfs[e3] []
atkfs[f3] []
atkfs[g3] []
atkfs[h3] []
atkfs[a4] []
atkfs[b4] []
atkfs[c4] []
atkfs[d4] []
atkfs[e4] []
atkfs[f4] []
atkfs[g4] []
atkfs[h4] []
atkfs[a5] []
atkfs[b5] []
atkfs[c5] []
atkfs[d5] []
atkfs[e5] []
atkfs[f5] []
atkfs[g5] []
atkfs[h5] []
atkfs[a6] []
atkfs[b6] []
atkfs[c6] []
atkfs[d6] []
atkfs[e6] []
atkfs[f6] []
atkfs[g6] []
atkfs[h6] []
atkfs[a7] [b6]
atkfs[b7] [a6 c6]
atkfs[c7] [b6 d6]
atkfs[d7] [c6 e6]
atkfs[e7] [d6 f6]
atkfs[f7] [e6 g6]
atkfs[g7] [f6 h6]
atkfs[h7] [g6]
atkfs[a8] [a7 b8]
atkfs[b8] [a6 c6 d7]
atkfs[c8] [b7 d7]
atkfs[d8] [c7 d7 e7 c8 e8]
atkfs[e8] [d7 e7 f7 d8 f8]
atkfs[f8] [e7 g7]
atkfs[g8] [f6 h6 e7]
atkfs[h8] [h7 g8]
atkts[a1] []
atkts[b1] [a1]
atkts[c1] [d1]
atkts[d1] [e1]
atkts[e1] [d1]
atkts[f1] [e1]
atkts[g1] [h1]
atkts[h1] []
atkts[a2] [a1]
atkts[b2] [c1]
atkts[c2] [d1]
atkts[d2] [b1 c1 d1 e1]
atkts[e2] [d1 e1 f1 g1]
atkts[f2] [e1]
atkts[g2] [f1]
atkts[h2] [h1]
atkts[a3] [b1 b2]
atkts[b3] [a2 c2]
atkts[c3] [b1 b2 d2]
atkts[d3] [c2 e2]
atkts[e3] [d2 f2]
atkts[f3] [g1 e2 g2]
atkts[g3] [f2 h2]
atkts[h3] [g1 g2]
atkts[a4] []
atkts[b4] []
atkts[c4] []
atkts[d4] []
atkts[e4] []
atkts[f4] []
atkts[g4] []
atkts[h4] []
atkts[a5] []
atkts[b5] []
atkts[c5] []
atkts[d5] []
atkts[e5] []
atkts[f5] []
atkts[g5] []
atkts[h5] []
atkts[a6] [b7 b8]
atkts[b6] [a7 c7]
atkts[c6] [b7 d7 b8]
atkts[d6] [c7 e7]
atkts[e6] [d7 f7]
atkts[f6] [e7 g7 g8]
atkts[g6] [f7 h7]
atkts[h6] [g7 g8]
atkts[a7] [a8]
atkts[b7] [c8]
atkts[c7] [d8]
atkts[d7] [b8 c8 d8 e8]
atkts[e7] [d8 e8 f8 g8]
atkts[f7] [e8]
atkts[g7] [f8]
atkts[h7] [h8]
atkts[a8] []
atkts[b8] [a8]
atkts[c8] [d8]
atkts[d8] [e8]
atkts[e8] [d8]
atkts[f8] [e8]
atkts[g8] [h8]
atkts[h8] []
Aleks Peshkov
Posts: 892
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia

Re: Incremental or non-incremental PST evaluation calcs

Post by Aleks Peshkov »

Incremental PST update is more clean and easier solution, because you have to do similar incremental update of Zobrist keys each move anyway. It is even possible to do PST and hash key update together inside the same SSE register (practically for free).