Context: TestCalcValidatorProb_PythonReference in chain/ecfinality/calculator_test.go compares Go's reorg-probability calculations against reference values generated with scipy/numpy. The reference values were calibrated on a modern x86_64 CPU with FMA3 (fused multiply-add) enabled.
Problem: CPUs without FMA3 (Ivy Bridge / older, e.g. E5-16xx v2) produce results that drift by ~2e-5 relative at the deep-depth values (e.g. 4.627122e-12 vs 4.627232e-12 at depth=30), because math.Log and math.Pow use a different reduction order without the fma intrinsic. Those values are already close to float64 noise floor, but the current 1e-12 tolerance can't distinguish 'algorithm regression' from 'CPU FP behavior' at that scale.
Current workaround (2026-07-23, this issue's parent PR #141): t.Skip() on non-FMA x86_64. Preserves the tight 1e-12 tolerance on CPUs that can meet it, without loosening the check globally. Surfaced during the CI move to self-hosted runners on the Reiers home cluster (node1 = E5-1650v2, Ivy Bridge).
Proper long-term fix (pick one):
-
Per-CPU-class reference vectors. Generate a second set of reference values on a non-FMA CPU using the same scipy invocation; ship both, dispatch by cpu.X86.HasFMA. Keeps 1e-12 parity on every hardware target we care about.
-
FMA-locked implementation. Force the Go math to use / not use FMA consistently across CPUs (e.g. via math.FMA explicitly in CalcValidatorProb, or via a build tag + software fallback). Makes results deterministic across CPU generations at the cost of some throughput on modern chips.
-
Interval arithmetic. Store reference values as [lo, hi] intervals and check containment. Cleanest but requires reworking the reference generator.
Priority: low. Structural tests still exercise the algorithm on every CPU. The Python parity check remains active on modern CPUs (which is where the algorithm is actually run in production).
Context:
TestCalcValidatorProb_PythonReferenceinchain/ecfinality/calculator_test.gocompares Go's reorg-probability calculations against reference values generated with scipy/numpy. The reference values were calibrated on a modern x86_64 CPU with FMA3 (fused multiply-add) enabled.Problem: CPUs without FMA3 (Ivy Bridge / older, e.g. E5-16xx v2) produce results that drift by ~2e-5 relative at the deep-depth values (e.g.
4.627122e-12vs4.627232e-12at depth=30), becausemath.Logandmath.Powuse a different reduction order without the fma intrinsic. Those values are already close to float64 noise floor, but the current 1e-12 tolerance can't distinguish 'algorithm regression' from 'CPU FP behavior' at that scale.Current workaround (2026-07-23, this issue's parent PR #141):
t.Skip()on non-FMA x86_64. Preserves the tight 1e-12 tolerance on CPUs that can meet it, without loosening the check globally. Surfaced during the CI move to self-hosted runners on the Reiers home cluster (node1 = E5-1650v2, Ivy Bridge).Proper long-term fix (pick one):
Per-CPU-class reference vectors. Generate a second set of reference values on a non-FMA CPU using the same scipy invocation; ship both, dispatch by
cpu.X86.HasFMA. Keeps 1e-12 parity on every hardware target we care about.FMA-locked implementation. Force the Go math to use / not use FMA consistently across CPUs (e.g. via
math.FMAexplicitly inCalcValidatorProb, or via a build tag + software fallback). Makes results deterministic across CPU generations at the cost of some throughput on modern chips.Interval arithmetic. Store reference values as
[lo, hi]intervals and check containment. Cleanest but requires reworking the reference generator.Priority: low. Structural tests still exercise the algorithm on every CPU. The Python parity check remains active on modern CPUs (which is where the algorithm is actually run in production).