Skip to content

Commit

Permalink
🔍 Tune search values (spsa) + define limits and steps in code (#730)
Browse files Browse the repository at this point in the history
- Add `SPSAAttribute<T>` to define in the code param limits and steps
- Use it for all search tunable parameters in `Configuration.EngineSettings`
- Add `NMP_DepthIncrement` and `NMP_DepthDivisor` instead of having them hardcoded
- Fix `see_badcapturereduction` UCI handling
- Add `ob_spsa`, `ob_spsa_pretty` and `wf_spsa` UCI commands to print spsa inputs

- Add WF tuned values
  • Loading branch information
eduherminio authored May 9, 2024
1 parent bfbec00 commit b4e88ae
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 27 deletions.
29 changes: 16 additions & 13 deletions src/Lynx.Cli/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,43 @@
"UseOnlineTablebaseInSearch": false,
"OnlineTablebaseMaxSupportedPieces": 7,
"ShowWDL": false,
"SPSA_OB_R_end": 0.02,

"HardTimeBoundMultiplier": 0.52,
"SoftTimeBoundMultiplier": 1,
"DefaultMovesToGo": 45,
"SoftTimeBaseIncrementMultiplier": 0.8,

"LMR_MinDepth": 3,
"LMR_MinFullDepthSearchedMoves": 4,
"LMR_MinFullDepthSearchedMoves": 3,
"LMR_Base": 0.85,
"LMR_Divisor": 2.84,
"LMR_Divisor": 3.12,

"NMP_MinDepth": 3,
"NMP_BaseDepthReduction": 1,
"NMP_BaseDepthReduction": 2,
"NMP_DepthIncrement": 1,
"NMP_DepthDivisor": 5,

"AspirationWindow_Delta": 20,
"AspirationWindow_Delta": 12,
"AspirationWindow_MinDepth": 7,

"RFP_MaxDepth": 4,
"RFP_DepthScalingFactor": 87,
"RFP_MaxDepth": 6,
"RFP_DepthScalingFactor": 107,

"Razoring_MaxDepth": 3,
"Razoring_Depth1Bonus": 105,
"Razoring_NotDepth1Bonus": 161,
"Razoring_MaxDepth": 2,
"Razoring_Depth1Bonus": 84,
"Razoring_NotDepth1Bonus": 135,

"IIR_MinDepth": 2,
"IIR_MinDepth": 3,

"LMP_MaxDepth": 2,
"LMP_MaxDepth": 6,
"LMP_BaseMovesToTry": 0,
"LMP_MovesDepthMultiplier": 10,
"LMP_MovesDepthMultiplier": 4,

"History_MaxMoveValue": 8192,
"History_MaxMoveRawBonus": 1896,

"SEE_BadCaptureReduction": 1,
"SEE_BadCaptureReduction": 2,

// Evaluation
"DoubledPawnPenalty": {
Expand Down
54 changes: 41 additions & 13 deletions src/Lynx/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ public sealed class EngineSettings

public bool ShowWDL { get; set; } = false;

public double SPSA_OB_R_end { get; set; } = 0.02;

#region Time management

public double HardTimeBoundMultiplier { get; set; } = 0.52;
Expand All @@ -115,45 +117,68 @@ public sealed class EngineSettings

#endregion

[SPSAAttribute<int>(2, 10, 1)]
public int LMR_MinDepth { get; set; } = 3;

public int LMR_MinFullDepthSearchedMoves { get; set; } = 4;
[SPSAAttribute<int>(1, 10, 1)]
public int LMR_MinFullDepthSearchedMoves { get; set; } = 3;

/// <summary>
/// Value originally from Stormphrax, who apparently took it from Viridithas
/// </summary>
[SPSAAttribute<double>(0.1, 2, 0.1)]
public double LMR_Base { get; set; } = 0.85;

/// <summary>
/// Value originally from Akimbo
/// </summary>
public double LMR_Divisor { get; set; } = 2.84;
[SPSAAttribute<double>(1, 5, 0.1)]
public double LMR_Divisor { get; set; } = 3.12;

[SPSAAttribute<int>(1, 10, 1)]
public int NMP_MinDepth { get; set; } = 3;

public int NMP_BaseDepthReduction { get; set; } = 1;
[SPSAAttribute<int>(1, 5, 1)]
public int NMP_BaseDepthReduction { get; set; } = 2;

[SPSAAttribute<int>(0, 20, 1)]
public int NMP_DepthIncrement { get; set; } = 1;

[SPSAAttribute<int>(1, 20, 1)]
public int NMP_DepthDivisor { get; set; } = 5;

public int AspirationWindow_Delta { get; set; } = 20;
[SPSAAttribute<int>(1, 100, 5)]
public int AspirationWindow_Delta { get; set; } = 12;

[SPSAAttribute<int>(1, 20, 1)]
public int AspirationWindow_MinDepth { get; set; } = 7;

public int RFP_MaxDepth { get; set; } = 4;
[SPSAAttribute<int>(1, 20, 1)]
public int RFP_MaxDepth { get; set; } = 6;

public int RFP_DepthScalingFactor { get; set; } = 87;
[SPSAAttribute<int>(1, 300, 5)]
public int RFP_DepthScalingFactor { get; set; } = 107;

public int Razoring_MaxDepth { get; set; } = 3;
[SPSAAttribute<int>(1, 10, 1)]
public int Razoring_MaxDepth { get; set; } = 2;

public int Razoring_Depth1Bonus { get; set; } = 105;
[SPSAAttribute<int>(1, 300, 10)]
public int Razoring_Depth1Bonus { get; set; } = 84;

public int Razoring_NotDepth1Bonus { get; set; } = 161;
[SPSAAttribute<int>(1, 300, 10)]
public int Razoring_NotDepth1Bonus { get; set; } = 135;

public int IIR_MinDepth { get; set; } = 2;
[SPSAAttribute<int>(1, 10, 1)]
public int IIR_MinDepth { get; set; } = 3;

public int LMP_MaxDepth { get; set; } = 2;
[SPSAAttribute<int>(1, 10, 1)]
public int LMP_MaxDepth { get; set; } = 6;

[SPSAAttribute<int>(0, 10, 1)]
public int LMP_BaseMovesToTry { get; set; } = 0;

public int LMP_MovesDepthMultiplier { get; set; } = 10;
[SPSAAttribute<int>(0, 50, 1)]
public int LMP_MovesDepthMultiplier { get; set; } = 4;

public int History_MaxMoveValue { get; set; } = 8_192;

Expand All @@ -162,7 +187,8 @@ public sealed class EngineSettings
/// </summary>
public int History_MaxMoveRawBonus { get; set; } = 1_896;

public int SEE_BadCaptureReduction { get; set; } = 1;
[SPSAAttribute<int>(0, 6, 1)]
public int SEE_BadCaptureReduction { get; set; } = 2;

#region Evaluation

Expand Down Expand Up @@ -309,6 +335,8 @@ public override string ToString()
[JsonSerializable(typeof(EngineSettings))]
[JsonSerializable(typeof(TaperedEvaluationTerm))]
[JsonSerializable(typeof(TaperedEvaluationTermByRank))]
[JsonSerializable(typeof(SPSAAttribute<int>))]
[JsonSerializable(typeof(SPSAAttribute<double>))]
internal partial class EngineSettingsJsonSerializerContext : JsonSerializerContext
{
}
85 changes: 85 additions & 0 deletions src/Lynx/SPSAAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Numerics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Lynx;

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
internal class SPSAAttribute<T> : Attribute
where T : INumberBase<T>, IMultiplyOperators<T, T, T>, IConvertible, IParsable<T>, ISpanParsable<T>
{
private static readonly T _hundred;

public T MinValue { get; }
public T MaxValue { get; }
public T Step { get; }

#pragma warning disable S3963 // "static" fields should be initialized inline
static SPSAAttribute()
#pragma warning restore S3963 // "static" fields should be initialized inline
{
_hundred = T.Zero;
for (int i = 0; i < 100; i++)
{
_hundred += T.One;
}
}

public SPSAAttribute(T minValue, T maxValue, T step)
{
if (typeof(T) == typeof(double))
{
minValue *= _hundred;
maxValue *= _hundred;
step *= _hundred;
}

MinValue = minValue;
MaxValue = maxValue;
Step = step;
}

public string ToOBString(PropertyInfo property)
{
T val = GetPropertyValue(property);

return $"{property.Name}, int, {val}, {MinValue}, {MaxValue}, {Step}, {Configuration.EngineSettings.SPSA_OB_R_end}";
}

private static T GetPropertyValue(PropertyInfo property)
{
T val = (T)property.GetValue(Configuration.EngineSettings)!;

if (typeof(T) == typeof(double))
{
val *= _hundred;
}

return val;
}

public string ToOBPrettyString(PropertyInfo property)
{
T val = GetPropertyValue(property);

return $"{property.Name,-35} {"int",-5} {val,-5} {MinValue,-5} {MaxValue,-5} {Step,-5} {Configuration.EngineSettings.SPSA_OB_R_end,-5}";
}

public KeyValuePair<string, JsonNode?> ToWeatherFactoryString(PropertyInfo property)
{
T val = GetPropertyValue(property);

#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
return KeyValuePair.Create(
property.Name,
JsonSerializer.SerializeToNode(new
{
value = val,
min_value = MinValue,
max_value = MaxValue,
step = Step,
}));
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
}
}
3 changes: 2 additions & 1 deletion src/Lynx/Search/NegaMax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM
&& phase > 2 // Zugzwang risk reduction: pieces other than pawn presents
&& (ttElementType != NodeType.Alpha || ttEvaluation >= beta)) // TT suggests NMP will fail: entry must not be a fail-low entry with a score below beta - Stormphrax and Ethereal
{
var nmpReduction = Configuration.EngineSettings.NMP_BaseDepthReduction + ((depth + 1) / 3); // Clarity
var nmpReduction = Configuration.EngineSettings.NMP_BaseDepthReduction + ((depth + Configuration.EngineSettings.NMP_DepthIncrement) / Configuration.EngineSettings.NMP_DepthDivisor); // Clarity

// TODO more advanced adaptative reduction, similar to what Ethereal and Stormphrax are doing
//var nmpReduction = Math.Min(
Expand Down Expand Up @@ -306,6 +306,7 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM
&& scores[moveIndex] >= EvaluationConstants.BadCaptureMoveBaseScoreValue)
{
reduction += Configuration.EngineSettings.SEE_BadCaptureReduction;
reduction = Math.Clamp(reduction, 0, depth - 1);
}

// Search with reduced depth
Expand Down
3 changes: 3 additions & 0 deletions src/Lynx/UCI/Commands/Engine/OptionCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public sealed class OptionCommand : EngineBaseCommand
//$"option name {nameof(Configuration.EngineSettings.LMR_Divisor)} type spin default {100 * Configuration.EngineSettings.LMR_Divisor} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.NMP_MinDepth)} type spin default {Configuration.EngineSettings.NMP_MinDepth} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.NMP_BaseDepthReduction)} type spin default {Configuration.EngineSettings.NMP_BaseDepthReduction} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.NMP_DepthIncrement)} type spin default {Configuration.EngineSettings.NMP_DepthIncrement} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.NMP_DepthDivisor)} type spin default {Configuration.EngineSettings.NMP_DepthDivisor} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.AspirationWindow_Delta)} type spin default {Configuration.EngineSettings.AspirationWindow_Delta} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.AspirationWindow_MinDepth)} type spin default {Configuration.EngineSettings.AspirationWindow_MinDepth} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.RFP_MaxDepth)} type spin default {Configuration.EngineSettings.RFP_MaxDepth} min 0 max 1024",
Expand All @@ -151,6 +153,7 @@ public sealed class OptionCommand : EngineBaseCommand
//$"option name {nameof(Configuration.EngineSettings.LMP_MaxDepth)} type spin default {Configuration.EngineSettings.LMP_MaxDepth} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.LMP_BaseMovesToTry)} type spin default {Configuration.EngineSettings.LMP_BaseMovesToTry} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.LMP_MovesDepthMultiplier)} type spin default {Configuration.EngineSettings.LMP_MovesDepthMultiplier} min 0 max 1024",
//$"option name {nameof(Configuration.EngineSettings.SEE_BadCaptureReduction)} type spin default {Configuration.EngineSettings.SEE_BadCaptureReduction} min 0 max 1024",

#endregion
];
Expand Down
Loading

1 comment on commit b4e88ae

@eduherminio
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress check
40+0.4, Hash 256, no draw agreements or resignations, 8moves_v3.epd:

Score of Lynx 2920 - main vs Lynx 1.4.0: 1266 - 545 - 1689  [0.603] 3500
...      Lynx 2920 - main playing White: 691 - 247 - 812  [0.627] 1750
...      Lynx 2920 - main playing Black: 575 - 298 - 877  [0.579] 1750
...      White vs Black: 989 - 822 - 1689  [0.524] 3500
Elo difference: 72.6 +/- 8.3, LOS: 100.0 %, DrawRatio: 48.3 %
SPRT: llr 0 (0.0%), lbound -inf, ubound inf

Please sign in to comment.