about stockfish and logic

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
hgm
Posts: 27811
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: about stockfish and logic

Post by hgm »

mcostalba wrote:Instead a possible optimization that I didn't make because of didn't find a nice way to do it (read "without changing qsearch() call parameters") is to pass the static position evaluation to qsearch when we razor.
Wouldn't it be a better design of an engine in general to call evaluate in the parent node? In my new engine I am driven in that direction quite naturally. Because I incrementally update an attack map, MakeMove has become quite expensive, and I don't want to do a full MakeMove (and UnMake it immediately afterwards) for going to an 'empty' node. (I.e. a node where no moves will be searched, because I either have a stand-pat cutoff there, or all moves were futile.)

So I do the evaluation first for as much as I can, to make almost sure I will not have stand-pat cuttoff or a futile node, and only make the move if I have not.
Ralph Stoesser
Posts: 408
Joined: Sat Mar 06, 2010 9:28 am

Re: about stockfish and logic

Post by Ralph Stoesser »

mcostalba wrote:
Instead a possible optimization that I didn't make because of didn't find a nice way to do it (read "without changing qsearch() call parameters") is to pass the static position evaluation to qsearch when we razor.

IOW we have already called the costly evaluate() few lines before:

Code: Select all

ss[ply].eval = evaluate(pos, ei, threadID);
and now we are going to call qsearch that, if doesn't return immediately after a TT hit, is going to call _again_ evaluate() on the same position, but in this case we could "pass" to qsearch the already calculated score and avoid the second redundant call.

Someone has hints (read "a patch") to how to do it ?
Why not store static eval score in TT (VALUE_TYPE_EVAL) before calling qsearch()?

Disclaimer: I may be wrong. I'm still not very familar with all the details from Stockfish code.

Code: Select all

// Step 6. Razoring
    if (    refinedValue < beta - razor_margin&#40;depth&#41;
        &&  ttMove == MOVE_NONE
        &&  ss&#91;ply - 1&#93;.currentMove != MOVE_NULL
        &&  depth < RazorDepth
        && !isCheck
        && !value_is_mate&#40;beta&#41;
        && !pos.has_pawn_on_7th&#40;pos.side_to_move&#40;)))
    &#123;
    	if &#40;refinedValue != ss&#91;ply&#93;.eval&#41;
    		// refinedValue is from TT, so return it immediately
    		return refinedValue;

    	// Store the static eval score to avoid a costly evaluation&#40;) call in qsearch
    	TT.store&#40;pos.get_key&#40;), refinedValue, VALUE_TYPE_EVAL, Depth&#40;-127*OnePly&#41;, MOVE_NONE&#41;;

    	Value rbeta = beta - razor_margin&#40;depth&#41;;
    	Value v = qsearch&#40;pos, ss, rbeta-1, rbeta, Depth&#40;0&#41;, ply, threadID&#41;;
    	if &#40;v < rbeta&#41;
    		// Logically we should return &#40;v + razor_margin&#40;depth&#41;), but
    		// surprisingly this did slightly weaker in tests.
    		return v;

    &#125;
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: about stockfish and logic

Post by mcostalba »

Ralph Stoesser wrote: Why not store static eval score in TT (VALUE_TYPE_EVAL) before calling qsearch()?

Disclaimer: I may be wrong. I'm still not very familar with all the details from Stockfish code.
Hi Ralph,

your suggestion is not bad, and is also supported with the actual code ;-)

I had not implemented this to avoid overwriting an exsisting valuable TT entry with an eval score. If you look at TranspositionTable::store() in tt.cpp you see:

Code: Select all

if (!tte->key&#40;) || tte->key&#40;) == posKey32&#41; // empty or overwrite old
      &#123;
          // Do not overwrite when new type is VALUE_TYPE_EV_LO
          if &#40;tte->key&#40;) && t == VALUE_TYPE_EV_LO&#41;
              return;

          ...... cut ......

          *tte = TTEntry&#40;posKey32, v, t, d, m, generation&#41;;
          return;
      &#125;
So we have two possibilities

1) Leave the code as is and in this case we overwrite any exsisting TT entry (but I don't like it)

2) Modify the code in tt.cpp as:

Code: Select all

          // Do not overwrite when new entry is a static evaluation
          if &#40;tte->key&#40;) && &#40;t & VALUE_TYPE_EVAL&#41;)
              return;
in this case your suggestion works only when we don't have any exsisting TT entry.

Next step is to _measure_ what we are talking about, so after adding dbg_hit_on() statistical tracing in the code:

Code: Select all

    &#123;
        Value rbeta = beta - razor_margin&#40;depth&#41;;
        
        dbg_hit_on&#40;!tte&#41;;
        
        Value v = qsearch&#40;pos, ss, rbeta-1, rbeta, Depth&#40;0&#41;, ply, threadID&#41;;
        if &#40;v < rbeta&#41;
            // Logically we should return &#40;v + razor_margin&#40;depth&#41;), but
            // surprisingly this did slightly weaker in tests.
            return v;
    &#125;
we found that on a set of typical positions in the 66% of cases we don't have any pre-exsisting TT entry and so the trick works.

The trick doesn't work for remaining more then 40% of cases. If we think that 60% is enough, the last step is to build up a patch and test the idea on some thousands games ;-)

Perhaps I will do, I still don't know, but I just wanted to explicitly write all the steps involved in how a typical modification is analysed, evaluated, and then tested with real games before to be committed: this is how we develop in SF.
Ralph Stoesser
Posts: 408
Joined: Sat Mar 06, 2010 9:28 am

Re: about stockfish and logic

Post by Ralph Stoesser »

Marco,

Yes, I understand.

I think there is nothing big to gain anyways. After 1000 10 sec self play games I can see nothing world-shuttering.

Using global (per thread) variables last_eval, last_pos_key or always passing by the eval as a parameter to the child nodes may bring a tiny performance gain. I somewhat like the suggestion from H.G.Muller, but at the end I think these kind of optimizations goes in the wrong direction anyhow. Better to work on the eval function now and wait for bug reports regarding the search. Nitpicking performance optimizations can wait until the iPhone version plays at 3000 ELO. :D
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: about stockfish and logic

Post by mcostalba »

hgm wrote: So I do the evaluation first for as much as I can, to make almost sure I will not have stand-pat cuttoff or a futile node, and only make the move if I have not.
But to correctly evakluate a node you need the move done. I mean board rapresentation and hash keys should be already updated.

What I could try is to do the TT lookup before to do the move, in this case should be a bit easier...but I am not sure.
User avatar
hgm
Posts: 27811
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: about stockfish and logic

Post by hgm »

But I don't need the attack map fully updated to do the evaluation. Just moving the piece on the board is not that much work. The hash key is already calculated even before I start making the move, to test for repetition, and to do the hash probe. If there is a hash cutoff or repetition, there is no need to make the move.

Only the mobility term of the evaluation shares a lot of work with the update of the attack map, but it is quite small. The evaluation without it must really be within a very close margin from the window limit before it becomes necessary to really calculate it. In most cases you can decide on the fail low or fail high without it.
Uri Blass
Posts: 10309
Joined: Thu Mar 09, 2006 12:37 am
Location: Tel-Aviv Israel

Re: about stockfish and logic

Post by Uri Blass »

Not only mobility

What about king safety?
You give bonus for attacking squares near the king and you need the attack map.

What about evaluating passed pawns that can safely progress?
you also may give a bonus for a passed pawn based on the question if the passed pawn can progress safely(and it may be very important for pawns in the 7th rank)
User avatar
michiguel
Posts: 6401
Joined: Thu Mar 09, 2006 8:30 pm
Location: Chicago, Illinois, USA

Re: about stockfish and logic

Post by michiguel »

hgm wrote:But I don't need the attack map fully updated to do the evaluation. Just moving the piece on the board is not that much work. The hash key is already calculated even before I start making the move, to test for repetition, and to do the hash probe. If there is a hash cutoff or repetition, there is no need to make the move.

Only the mobility term of the evaluation shares a lot of work with the update of the attack map, but it is quite small. The evaluation without it must really be within a very close margin from the window limit before it becomes necessary to really calculate it. In most cases you can decide on the fail low or fail high without it.
I do something similar in Gaviota. I evaluate after makemove, but I do not make a full makemove. I do some sort of lazy_makemove, and lazy_unmakemove. It will be more useful when I implement lazy_eval, which I have not done yet. Still, it saves some time because sometimes I need to return w/o evaluating.

Miguel
User avatar
hgm
Posts: 27811
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: about stockfish and logic

Post by hgm »

Uri Blass wrote:Not only mobility

What about king safety?
You give bonus for attacking squares near the king and you need the attack map.

What about evaluating passed pawns that can safely progress?
you also may give a bonus for a passed pawn based on the question if the passed pawn can progress safely(and it may be very important for pawns in the 7th rank)
I don't keep attack maps for empty squares. That would not pay off, as it is quite expensive, and only for very few squares the information would be used. The attack map only serves to be able to quickly test if there are non-futile captures.