Merge pull request #31 from mlemanczyk/codex/modify-evenperfectbitscanner-for-uint128-divisors

Allow residue GPU scanning to cover UInt128 divisors
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-09-20 02:49:36 +02:00
commit ff2c516a0e
6 files changed
+62 -56

No files matched your search

+24 -18
View File
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
@@ -73,12 +74,13 @@ internal static class Program
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
bool orderOnGpu = true; // controls order computations device bool orderOnGpu = true; // controls order computations device
int scanBatchSize = 2_097_152, sliceSize = 32; int scanBatchSize = 2_097_152, sliceSize = 32;
ulong residueKMax = 5_000_000UL; UInt128 residueKMax = 5_000_000UL;
string filterFile = string.Empty; string filterFile = string.Empty;
string cyclesPath = DefaultCyclesPath; string cyclesPath = DefaultCyclesPath;
int cyclesBatchSize = 512; int cyclesBatchSize = 512;
bool continueCyclesGeneration = false; bool continueCyclesGeneration = false;
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; UInt128 divisorCyclesSearchLimit128 = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
// NTT backend selection (GPU): reference vs staged // NTT backend selection (GPU): reference vs staged
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
@@ -140,19 +142,23 @@ internal static class Program
int eq = arg.IndexOf('='); int eq = arg.IndexOf('=');
divisor = UInt128.Parse(arg.AsSpan(eq + 1)); divisor = UInt128.Parse(arg.AsSpan(eq + 1));
} }
else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
{ {
int eq = arg.IndexOf('='); int eq = arg.IndexOf('=');
divisorCyclesSearchLimit = ulong.Parse(arg.AsSpan(eq + 1)); if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var limit))
} {
else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) divisorCyclesSearchLimit128 = limit;
{ divisorCyclesSearchLimit = limit > (UInt128)ulong.MaxValue ? ulong.MaxValue : (ulong)limit;
int eq = arg.IndexOf('='); }
if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax)) }
{ else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
residueKMax = kmax; {
} int eq = arg.IndexOf('=');
} if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var kmax))
{
residueKMax = kmax;
}
}
// Replaces --lucas=cpu|gpu; controls device for Lucas and for // Replaces --lucas=cpu|gpu; controls device for Lucas and for
// incremental/pow2mod scanning // incremental/pow2mod scanning
else if (arg.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase)) else if (arg.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
@@ -448,8 +454,8 @@ internal static class Program
useGpuScan: mersenneOnGpu, useGpuScan: mersenneOnGpu,
useGpuOrder: orderOnGpu, useGpuOrder: orderOnGpu,
useResidue: useResidue, useResidue: useResidue,
maxK: residueKMax, maxK: residueKMax,
residueDivisorSets: divisorCyclesSearchLimit); residueDivisorSets: divisorCyclesSearchLimit128);
if (!useLucas) if (!useLucas)
{ {
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL); tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
@@ -741,7 +747,7 @@ internal static class Program
Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)"); Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)");
Console.WriteLine(" --divisor-cycles-batch-size=<value> batch size for cycles generation (default 512)"); Console.WriteLine(" --divisor-cycles-batch-size=<value> batch size for cycles generation (default 512)");
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation"); Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor or residue"); Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor or residue (UInt128 supported; residue path honors full range)");
Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --use-order test primality via q order");
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
@@ -30,7 +30,7 @@ public class MersenneNumberResidueCpuTesterTests
(UInt128)exponent << 1, (UInt128)exponent << 1,
LastDigitIsSeven(exponent), LastDigitIsSeven(exponent),
(UInt128)maxK, (UInt128)maxK,
1UL, UInt128.One,
(UInt128)maxK, (UInt128)maxK,
ref isPrime, ref isPrime,
ref exhausted); ref exhausted);
@@ -33,7 +33,7 @@ public class MersenneNumberResidueGpuTesterTests
(UInt128)exponent << 1, (UInt128)exponent << 1,
LastDigitIsSeven(exponent), LastDigitIsSeven(exponent),
(UInt128)maxK, (UInt128)maxK,
1UL, UInt128.One,
(UInt128)maxK, (UInt128)maxK,
ref isPrime, ref isPrime,
ref exhausted); ref exhausted);
@@ -10,12 +10,12 @@ public class MersenneNumberResidueCpuTester
UInt128 twoP, UInt128 twoP,
bool lastIsSeven, bool lastIsSeven,
UInt128 perSetLimit, UInt128 perSetLimit,
ulong setCount, UInt128 setCount,
UInt128 overallLimit, UInt128 overallLimit,
ref bool isPrime, ref bool isPrime,
ref bool divisorsExhausted) ref bool divisorsExhausted)
{ {
if (setCount == 0UL || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero) if (setCount == UInt128.Zero || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero)
{ {
return; return;
} }
@@ -52,9 +52,9 @@ public class MersenneNumberResidueCpuTester
ModResidueTracker tracker = _mersenneResidueTracker!; ModResidueTracker tracker = _mersenneResidueTracker!;
UInt128 limitInclusive = overallLimit + UInt128.One; UInt128 limitInclusive = overallLimit + UInt128.One;
for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{ {
UInt128 setOffset = perSetLimit * (UInt128)setIndex; UInt128 setOffset = perSetLimit * setIndex;
UInt128 k = setOffset + UInt128.One; UInt128 k = setOffset + UInt128.One;
if (k >= limitInclusive) if (k >= limitInclusive)
{ {
@@ -12,20 +12,20 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// via GpuKernelPool to avoid host round-trips. // via GpuKernelPool to avoid host round-trips.
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
public void Scan( public void Scan(
ulong exponent, ulong exponent,
UInt128 twoP, UInt128 twoP,
bool lastIsSeven, bool lastIsSeven,
UInt128 perSetLimit, UInt128 perSetLimit,
ulong setCount, UInt128 setCount,
UInt128 overallLimit, UInt128 overallLimit,
ref bool isPrime, ref bool isPrime,
ref bool divisorsExhausted) ref bool divisorsExhausted)
{ {
if (setCount == 0UL || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero) if (setCount == UInt128.Zero || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero)
{ {
return; return;
} }
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
@@ -48,9 +48,9 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
Span<ulong> orders = orderArray.AsSpan(0, batchSize); Span<ulong> orders = orderArray.AsSpan(0, batchSize);
try try
{ {
for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{ {
UInt128 setOffset = perSetLimit * (UInt128)setIndex; UInt128 setOffset = perSetLimit * setIndex;
UInt128 setStart = setOffset + UInt128.One; UInt128 setStart = setOffset + UInt128.One;
if (setStart >= limitInclusive) if (setStart >= limitInclusive)
{ {
+15 -15
View File
@@ -25,13 +25,13 @@ public sealed class MersenneNumberTester(
bool useGpuScan = true, bool useGpuScan = true,
bool useGpuOrder = true, bool useGpuOrder = true,
bool useResidue = true, bool useResidue = true,
ulong maxK = 5_000_000UL, UInt128? maxK = null,
ulong residueDivisorSets = PerfectNumberConstants.ExtraDivisorCycleSearchLimit) UInt128? residueDivisorSets = null)
{ {
private readonly bool _useResidue = useResidue; private readonly bool _useResidue = useResidue;
private readonly bool _useIncremental = useIncremental && !useResidue; private readonly bool _useIncremental = useIncremental && !useResidue;
private readonly ulong _maxK = maxK; private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL;
private readonly ulong _residueDivisorSetCount = residueDivisorSets; private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useModuloWorkaround = useModuloWorkaround; private readonly bool _useModuloWorkaround = useModuloWorkaround;
private readonly bool _useGpuLucas = useGpuLucas; private readonly bool _useGpuLucas = useGpuLucas;
@@ -44,7 +44,7 @@ public sealed class MersenneNumberTester(
private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType); private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType);
private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new(); private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new();
private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new(); private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new();
private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null; private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null;
private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null; private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null;
// TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
@@ -246,17 +246,17 @@ public sealed class MersenneNumberTester(
// In residue mode the residue scan is the final primality check. // In residue mode the residue scan is the final primality check.
if (_useResidue) if (_useResidue)
{ {
if ((UInt128)_maxK < maxK) if (_maxK < maxK)
{ {
maxK = _maxK; maxK = _maxK;
} }
if (_maxK == 0UL || _residueDivisorSetCount == 0UL) if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero)
{ {
return prePrime; return prePrime;
} }
UInt128 totalLimit = (UInt128)_maxK; UInt128 totalLimit = _maxK;
if (totalLimit > maxK) if (totalLimit > maxK)
{ {
totalLimit = maxK; totalLimit = maxK;
@@ -267,23 +267,23 @@ public sealed class MersenneNumberTester(
return true; return true;
} }
ulong requestedSets = _residueDivisorSetCount; UInt128 requestedSets = _residueDivisorSetCount;
if (requestedSets == 0UL) if (requestedSets == UInt128.Zero)
{ {
requestedSets = 1UL; requestedSets = UInt128.One;
} }
UInt128 perSetLimit = DivideRoundUp(totalLimit, (UInt128)requestedSets); UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets);
if (perSetLimit == UInt128.Zero) if (perSetLimit == UInt128.Zero)
{ {
perSetLimit = UInt128.One; perSetLimit = UInt128.One;
} }
UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit); UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit);
ulong effectiveSets = requestedSets; UInt128 effectiveSets = requestedSets;
if (setsNeeded < (UInt128)effectiveSets) if (setsNeeded < effectiveSets)
{ {
effectiveSets = (ulong)setsNeeded; effectiveSets = setsNeeded;
} }
bool scanCompleted = false; bool scanCompleted = false;