Fix odd power table updates in GPU heuristics

This commit is contained in:
Marek Lemańczyk committed 2025-10-05 16:55:27 +02:00
1 parent ce1355c749
commit 20120f5bc0
11 files changed
+1865 -141

No files matched your search

@@ -0,0 +1,418 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using FluentAssertions;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu;
[Collection("GpuNtt")]
public class GpuKernelCompilationTests
{
private static readonly MethodInfo DivisorCycleCacheLoadKernelMethod =
typeof(DivisorCycleCache).GetMethod("LoadKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("DivisorCycleCache.LoadKernel not found.");
private static readonly MethodInfo NttGetBitReverseKernelMethod =
typeof(NttGpuMath).GetMethod("GetBitReverseKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetBitReverseKernel not found.");
private static readonly MethodInfo NttGetMulKernelMethod =
typeof(NttGpuMath).GetMethod("GetMulKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetMulKernel not found.");
private static readonly MethodInfo NttGetStageKernelMethod =
typeof(NttGpuMath).GetMethod("GetStageKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetStageKernel not found.");
private static readonly MethodInfo NttGetScaleKernelMethod =
typeof(NttGpuMath).GetMethod("GetScaleKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetScaleKernel not found.");
private static readonly MethodInfo NttGetStageMontKernelMethod =
typeof(NttGpuMath).GetMethod("GetStageMontKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetStageMontKernel not found.");
private static readonly MethodInfo NttGetStageBarrettKernelMethod =
typeof(NttGpuMath).GetMethod("GetStageBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetStageBarrett128Kernel not found.");
private static readonly MethodInfo NttGetScaleBarrettKernelMethod =
typeof(NttGpuMath).GetMethod("GetScaleBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetScaleBarrett128Kernel not found.");
private static readonly MethodInfo NttGetSquareBarrettKernelMethod =
typeof(NttGpuMath).GetMethod("GetSquareBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetSquareBarrett128Kernel not found.");
private static readonly MethodInfo NttGetToMont64KernelMethod =
typeof(NttGpuMath).GetMethod("GetToMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetToMont64Kernel not found.");
private static readonly MethodInfo NttGetFromMont64KernelMethod =
typeof(NttGpuMath).GetMethod("GetFromMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetFromMont64Kernel not found.");
private static readonly MethodInfo NttGetSquareMont64KernelMethod =
typeof(NttGpuMath).GetMethod("GetSquareMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetSquareMont64Kernel not found.");
private static readonly MethodInfo NttGetScaleMont64KernelMethod =
typeof(NttGpuMath).GetMethod("GetScaleMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetScaleMont64Kernel not found.");
private static readonly MethodInfo NttGetForwardKernelMethod =
typeof(NttGpuMath).GetMethod("GetForwardKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetForwardKernel not found.");
private static readonly MethodInfo NttGetInverseKernelMethod =
typeof(NttGpuMath).GetMethod("GetInverseKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("NttGpuMath.GetInverseKernel not found.");
private static readonly MethodInfo DivisorByDivisorGetKernelMethod =
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernel not found.");
private static readonly MethodInfo DivisorByDivisorGetExponentKernelMethod =
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernelByPrimeExponent", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernelByPrimeExponent not found.");
private static readonly MethodInfo DivisorByDivisorGetRemainderDeltaKernelMethod =
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetRemainderDeltaKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetRemainderDeltaKernel not found.");
private static readonly MethodInfo DivisorByDivisorGetRemainderScanKernelMethod =
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetRemainderScanKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetRemainderScanKernel not found.");
private static readonly MethodInfo DivisorTesterGetKernelMethod =
typeof(MersenneNumberDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorGpuTester.GetKernel not found.");
private static readonly MethodInfo LucasLehmerGetKernelMethod =
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetKernel not found.");
private static readonly MethodInfo LucasLehmerGetAddSmallKernelMethod =
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetAddSmallKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetAddSmallKernel not found.");
private static readonly MethodInfo LucasLehmerGetSubSmallKernelMethod =
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetSubSmallKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetSubSmallKernel not found.");
private static readonly MethodInfo LucasLehmerGetReduceKernelMethod =
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetReduceKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetReduceKernel not found.");
private static readonly MethodInfo LucasLehmerGetIsZeroKernelMethod =
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetIsZeroKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetIsZeroKernel not found.");
private static readonly MethodInfo LucasLehmerGetBatchKernelMethod =
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetBatchKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetBatchKernel not found.");
private static readonly MethodInfo PrimeOrderGetPartialFactorKernelMethod =
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPartialFactorKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPartialFactorKernel not found.");
private static readonly MethodInfo PrimeOrderGetOrderKernelMethod =
typeof(PrimeOrderGpuHeuristics).GetMethod("GetOrderKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetOrderKernel not found.");
private static readonly MethodInfo PrimeOrderGetPow2ModKernelMethod =
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
(Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>)Delegate.CreateDelegate(
typeof(Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>),
typeof(MersenneDivisorCycles).GetMethod("GpuDivisorCycleKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("MersenneDivisorCycles.GpuDivisorCycleKernel not found."));
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
(Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>)Delegate.CreateDelegate(
typeof(Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>),
typeof(PrimeTester).GetMethod("SmallPrimeSieveKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeTester.SmallPrimeSieveKernel not found."));
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
(Action<Index1D, ArrayView<ulong>, ArrayView<byte>>)Delegate.CreateDelegate(
typeof(Action<Index1D, ArrayView<ulong>, ArrayView<byte>>),
typeof(PrimeTester).GetMethod("SharesFactorKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeTester.SharesFactorKernel not found."));
public static IEnumerable<object[]> KernelLoaders()
{
yield return Loader("DivisorCycleCache.GpuAdvanceDivisorCyclesKernel", CompileDivisorCycleCacheKernel);
yield return Loader("NttGpuMath.BitReverseKernel", CompileNttBitReverseKernel);
yield return Loader("NttGpuMath.MulKernel", CompileNttMulKernel);
yield return Loader("NttGpuMath.StageKernel", CompileNttStageKernel);
yield return Loader("NttGpuMath.ScaleKernel", CompileNttScaleKernel);
yield return Loader("NttGpuMath.StageMontKernel", CompileNttStageMontKernel);
yield return Loader("NttGpuMath.StageBarrett128Kernel", CompileNttStageBarrettKernel);
yield return Loader("NttGpuMath.ScaleBarrett128Kernel", CompileNttScaleBarrettKernel);
yield return Loader("NttGpuMath.SquareBarrett128Kernel", CompileNttSquareBarrettKernel);
yield return Loader("NttGpuMath.ToMont64Kernel", CompileNttToMont64Kernel);
yield return Loader("NttGpuMath.FromMont64Kernel", CompileNttFromMont64Kernel);
yield return Loader("NttGpuMath.SquareMont64Kernel", CompileNttSquareMont64Kernel);
yield return Loader("NttGpuMath.ScaleMont64Kernel", CompileNttScaleMont64Kernel);
yield return Loader("NttGpuMath.ForwardKernel", CompileNttForwardKernel);
yield return Loader("NttGpuMath.InverseKernel", CompileNttInverseKernel);
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.CheckKernel", CompileDivisorByDivisorCheckKernel);
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.ComputeMontgomeryExponentKernel", CompileDivisorByDivisorExponentKernel);
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.ComputeRemainderDeltasKernel", CompileDivisorByDivisorRemainderDeltaKernel);
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.AccumulateRemaindersKernel", CompileDivisorByDivisorRemainderScanKernel);
yield return Loader("MersenneNumberDivisorGpuTester.Kernel", CompileDivisorTesterKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.Kernel", CompileLucasLehmerKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.AddSmallKernel", CompileLucasLehmerAddSmallKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.SubtractSmallKernel", CompileLucasLehmerSubSmallKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.ReduceModMersenneKernel", CompileLucasLehmerReduceKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.IsZeroKernel", CompileLucasLehmerIsZeroKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.KernelBatch", CompileLucasLehmerBatchKernel);
yield return Loader("PrimeOrderGpuHeuristics.PartialFactorKernel", CompilePrimeOrderPartialFactorKernel);
yield return Loader("PrimeOrderGpuHeuristics.CalculateOrderKernel", CompilePrimeOrderKernel);
yield return Loader("PrimeOrderGpuHeuristics.Pow2ModKernel", CompilePrimeOrderPow2ModKernel);
yield return Loader("PrimeOrderGpuHeuristics.Pow2ModKernelWide", CompilePrimeOrderPow2ModWideKernel);
yield return Loader("MersenneDivisorCycles.GpuDivisorCycleKernel", CompileMersenneDivisorCyclesKernel);
yield return Loader("PrimeTester.SmallPrimeSieveKernel", CompilePrimeTesterSmallPrimeKernel);
yield return Loader("PrimeTester.SharesFactorKernel", CompilePrimeTesterSharesFactorKernel);
yield return Loader("GpuKernelPool.KernelLeaseKernels", CompileGpuKernelPoolKernels);
}
private static object[] Loader(string name, Action<Accelerator> loader)
{
return new object[] { name, loader };
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(KernelLoaders))]
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
{
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
var accelerator = lease.Accelerator;
Action action = () => compile(accelerator);
action.Should().NotThrow($"kernel {name} should compile");
}
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
{
_ = DivisorCycleCacheLoadKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttBitReverseKernel(Accelerator accelerator)
{
_ = NttGetBitReverseKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttMulKernel(Accelerator accelerator)
{
_ = NttGetMulKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttStageKernel(Accelerator accelerator)
{
_ = NttGetStageKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttScaleKernel(Accelerator accelerator)
{
_ = NttGetScaleKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttStageMontKernel(Accelerator accelerator)
{
_ = NttGetStageMontKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttStageBarrettKernel(Accelerator accelerator)
{
_ = NttGetStageBarrettKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttScaleBarrettKernel(Accelerator accelerator)
{
_ = NttGetScaleBarrettKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttSquareBarrettKernel(Accelerator accelerator)
{
_ = NttGetSquareBarrettKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttToMont64Kernel(Accelerator accelerator)
{
_ = NttGetToMont64KernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttFromMont64Kernel(Accelerator accelerator)
{
_ = NttGetFromMont64KernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttSquareMont64Kernel(Accelerator accelerator)
{
_ = NttGetSquareMont64KernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttScaleMont64Kernel(Accelerator accelerator)
{
_ = NttGetScaleMont64KernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttForwardKernel(Accelerator accelerator)
{
_ = NttGetForwardKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileNttInverseKernel(Accelerator accelerator)
{
_ = NttGetInverseKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileDivisorByDivisorCheckKernel(Accelerator accelerator)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
_ = DivisorByDivisorGetKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileDivisorByDivisorExponentKernel(Accelerator accelerator)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
_ = DivisorByDivisorGetExponentKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileDivisorByDivisorRemainderDeltaKernel(Accelerator accelerator)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
_ = DivisorByDivisorGetRemainderDeltaKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileDivisorByDivisorRemainderScanKernel(Accelerator accelerator)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
_ = DivisorByDivisorGetRemainderScanKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileDivisorTesterKernel(Accelerator accelerator)
{
var tester = new MersenneNumberDivisorGpuTester();
_ = DivisorTesterGetKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileLucasLehmerKernel(Accelerator accelerator)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
_ = LucasLehmerGetKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileLucasLehmerAddSmallKernel(Accelerator accelerator)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
_ = LucasLehmerGetAddSmallKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileLucasLehmerSubSmallKernel(Accelerator accelerator)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
_ = LucasLehmerGetSubSmallKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileLucasLehmerReduceKernel(Accelerator accelerator)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
_ = LucasLehmerGetReduceKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileLucasLehmerIsZeroKernel(Accelerator accelerator)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
_ = LucasLehmerGetIsZeroKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileLucasLehmerBatchKernel(Accelerator accelerator)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
_ = LucasLehmerGetBatchKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompilePrimeOrderPartialFactorKernel(Accelerator accelerator)
{
_ = PrimeOrderGetPartialFactorKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompilePrimeOrderKernel(Accelerator accelerator)
{
_ = PrimeOrderGetOrderKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompilePrimeOrderPow2ModKernel(Accelerator accelerator)
{
_ = PrimeOrderGetPow2ModKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
{
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
}
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
{
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
_ = kernel;
}
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
{
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
_ = kernel;
}
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
{
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
_ = kernel;
}
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
{
_ = accelerator;
var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
try
{
_ = lease.OrderKernel;
_ = lease.IncrementalKernel;
_ = lease.Pow2ModKernel;
_ = lease.IncrementalOrderKernel;
_ = lease.Pow2ModOrderKernel;
}
finally
{
lease.Dispose();
}
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
try
{
_ = orderLease.OrderKernel;
_ = orderLease.IncrementalKernel;
_ = orderLease.Pow2ModKernel;
_ = orderLease.IncrementalOrderKernel;
_ = orderLease.Pow2ModOrderKernel;
}
finally
{
orderLease.Dispose();
}
}
}
@@ -11,10 +11,12 @@ namespace PerfectNumbers.Core.Tests;
public class MersenneNumberDivisorGpuTesterTests
{
[Theory]
[InlineData(3UL, 7UL, true)]
[InlineData(11UL, 23UL, true)]
[InlineData(7UL, 35UL, false)]
[InlineData(13UL, 23UL, false)]
[InlineData(33UL, 7UL, true)]
[InlineData(35UL, 31UL, true)]
[InlineData(37UL, 223UL, true)]
[InlineData(33UL, 13UL, false)]
[InlineData(35UL, 73UL, false)]
[InlineData(37UL, 227UL, false)]
[Trait("Category", "Fast")]
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
{
@@ -41,7 +43,7 @@ public class MersenneNumberDivisorGpuTesterTests
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
.SetValue(null, Array.Empty<(ulong, uint)>());
tester.IsPrime(11UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeFalse();
}
@@ -50,7 +52,7 @@ public class MersenneNumberDivisorGpuTesterTests
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
{
var tester = new MersenneNumberDivisorGpuTester();
tester.IsPrime(3UL, 7UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
}
@@ -59,7 +61,7 @@ public class MersenneNumberDivisorGpuTesterTests
public void IsPrime_accepts_large_search_limits()
{
var tester = new MersenneNumberDivisorGpuTester();
tester.IsPrime(3UL, 7UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
exhausted.Should().BeTrue();
}
@@ -68,15 +70,15 @@ public class MersenneNumberDivisorGpuTesterTests
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(11UL);
tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(5UL, out bool exhausted).Should().BeTrue();
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
exhausted.Should().BeTrue();
tester.IsPrime(7UL, out exhausted).Should().BeTrue();
tester.IsPrime(37UL, out exhausted).Should().BeFalse();
exhausted.Should().BeTrue();
tester.IsPrime(11UL, out exhausted).Should().BeFalse();
tester.IsPrime(43UL, out exhausted).Should().BeFalse();
exhausted.Should().BeTrue();
}
@@ -85,7 +87,7 @@ public class MersenneNumberDivisorGpuTesterTests
public void ByDivisor_tester_requires_configuration()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
Action act = () => tester.IsPrime(5UL, out _);
Action act = () => tester.IsPrime(31UL, out _);
act.Should().Throw<InvalidOperationException>();
}
@@ -94,16 +96,30 @@ public class MersenneNumberDivisorGpuTesterTests
public void ByDivisor_tester_respects_filter_based_limit()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(5UL);
tester.ConfigureFromMaxPrime(41UL);
tester.IsPrime(5UL, out _).Should().BeTrue();
tester.IsPrime(7UL, out bool exhausted).Should().BeTrue();
tester.IsPrime(31UL, out _).Should().BeTrue();
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
exhausted.Should().BeTrue();
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeTrue();
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
}
[Theory]
[InlineData(71UL, false)]
[InlineData(89UL, true)]
[InlineData(127UL, true)]
[Trait("Category", "Fast")]
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(exponent);
tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime);
exhausted.Should().BeTrue();
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
@@ -113,9 +129,9 @@ public class MersenneNumberDivisorGpuTesterTests
GpuBatchSize = 8,
};
tester.ConfigureFromMaxPrime(13UL);
tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeFalse();
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
}
@@ -124,20 +140,20 @@ public class MersenneNumberDivisorGpuTesterTests
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(13UL);
tester.ConfigureFromMaxPrime(43UL);
using var session = tester.CreateDivisorSession();
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
byte[] hits = new byte[primes.Length];
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
session.CheckDivisor(23UL, cycle23, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL);
session.CheckDivisor(223UL, cycle223, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
Array.Fill(hits, (byte)0);
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL);
session.CheckDivisor(31UL, cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL);
session.CheckDivisor(13367UL, cycle13367, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
}
[Fact]
@@ -149,16 +165,16 @@ public class MersenneNumberDivisorGpuTesterTests
GpuBatchSize = 2,
};
tester.ConfigureFromMaxPrime(17UL);
tester.ConfigureFromMaxPrime(47UL);
using var session = tester.CreateDivisorSession();
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length];
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
session.CheckDivisor(23UL, cycle23, primes, hits);
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL);
session.CheckDivisor(223UL, cycle223, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
}
[Fact]
@@ -170,16 +186,16 @@ public class MersenneNumberDivisorGpuTesterTests
BatchSize = 2,
};
tester.ConfigureFromMaxPrime(17UL);
tester.ConfigureFromMaxPrime(47UL);
using var session = tester.CreateDivisorSession();
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length];
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
session.CheckDivisor(23UL, cycle23, primes, hits);
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL);
session.CheckDivisor(223UL, cycle223, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
}
}
@@ -9,7 +9,7 @@ public class MersenneNumberIncrementalGpuTesterTests
{
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
[Theory]
[Theory(Skip = "Disabled until the GPU incremental cycle heuristics are ported.")]
[InlineData(GpuKernelType.Pow2Mod, false)]
[InlineData(GpuKernelType.Incremental, false)]
[InlineData(GpuKernelType.Pow2Mod, true)]
@@ -20,7 +20,8 @@ public class MersenneNumberIncrementalGpuTesterTests
if (type == GpuKernelType.Pow2Mod)
{
RunCase(tester, 11UL, 1UL, expectedPrime: false);
// Mirrored by the divisor GPU coverage to ensure the failing cases stay validated.
RunCase(tester, 37UL, 1UL, expectedPrime: false);
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
}
+690 -3
View File
@@ -1,6 +1,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ILGPU.Algorithms;
namespace PerfectNumbers.Core.Gpu;
@@ -20,6 +21,691 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSizeBits - 1);
private const int Pow2WindowMaxExponent = (1 << Pow2WindowSizeBits) - 1;
private struct Pow2OddPowerTable
{
public GpuUInt128 Element0;
public GpuUInt128 Element1;
public GpuUInt128 Element2;
public GpuUInt128 Element3;
public GpuUInt128 Element4;
public GpuUInt128 Element5;
public GpuUInt128 Element6;
public GpuUInt128 Element7;
public GpuUInt128 Element8;
public GpuUInt128 Element9;
public GpuUInt128 Element10;
public GpuUInt128 Element11;
public GpuUInt128 Element12;
public GpuUInt128 Element13;
public GpuUInt128 Element14;
public GpuUInt128 Element15;
public GpuUInt128 Element16;
public GpuUInt128 Element17;
public GpuUInt128 Element18;
public GpuUInt128 Element19;
public GpuUInt128 Element20;
public GpuUInt128 Element21;
public GpuUInt128 Element22;
public GpuUInt128 Element23;
public GpuUInt128 Element24;
public GpuUInt128 Element25;
public GpuUInt128 Element26;
public GpuUInt128 Element27;
public GpuUInt128 Element28;
public GpuUInt128 Element29;
public GpuUInt128 Element30;
public GpuUInt128 Element31;
public GpuUInt128 Element32;
public GpuUInt128 Element33;
public GpuUInt128 Element34;
public GpuUInt128 Element35;
public GpuUInt128 Element36;
public GpuUInt128 Element37;
public GpuUInt128 Element38;
public GpuUInt128 Element39;
public GpuUInt128 Element40;
public GpuUInt128 Element41;
public GpuUInt128 Element42;
public GpuUInt128 Element43;
public GpuUInt128 Element44;
public GpuUInt128 Element45;
public GpuUInt128 Element46;
public GpuUInt128 Element47;
public GpuUInt128 Element48;
public GpuUInt128 Element49;
public GpuUInt128 Element50;
public GpuUInt128 Element51;
public GpuUInt128 Element52;
public GpuUInt128 Element53;
public GpuUInt128 Element54;
public GpuUInt128 Element55;
public GpuUInt128 Element56;
public GpuUInt128 Element57;
public GpuUInt128 Element58;
public GpuUInt128 Element59;
public GpuUInt128 Element60;
public GpuUInt128 Element61;
public GpuUInt128 Element62;
public GpuUInt128 Element63;
public GpuUInt128 Element64;
public GpuUInt128 Element65;
public GpuUInt128 Element66;
public GpuUInt128 Element67;
public GpuUInt128 Element68;
public GpuUInt128 Element69;
public GpuUInt128 Element70;
public GpuUInt128 Element71;
public GpuUInt128 Element72;
public GpuUInt128 Element73;
public GpuUInt128 Element74;
public GpuUInt128 Element75;
public GpuUInt128 Element76;
public GpuUInt128 Element77;
public GpuUInt128 Element78;
public GpuUInt128 Element79;
public GpuUInt128 Element80;
public GpuUInt128 Element81;
public GpuUInt128 Element82;
public GpuUInt128 Element83;
public GpuUInt128 Element84;
public GpuUInt128 Element85;
public GpuUInt128 Element86;
public GpuUInt128 Element87;
public GpuUInt128 Element88;
public GpuUInt128 Element89;
public GpuUInt128 Element90;
public GpuUInt128 Element91;
public GpuUInt128 Element92;
public GpuUInt128 Element93;
public GpuUInt128 Element94;
public GpuUInt128 Element95;
public GpuUInt128 Element96;
public GpuUInt128 Element97;
public GpuUInt128 Element98;
public GpuUInt128 Element99;
public GpuUInt128 Element100;
public GpuUInt128 Element101;
public GpuUInt128 Element102;
public GpuUInt128 Element103;
public GpuUInt128 Element104;
public GpuUInt128 Element105;
public GpuUInt128 Element106;
public GpuUInt128 Element107;
public GpuUInt128 Element108;
public GpuUInt128 Element109;
public GpuUInt128 Element110;
public GpuUInt128 Element111;
public GpuUInt128 Element112;
public GpuUInt128 Element113;
public GpuUInt128 Element114;
public GpuUInt128 Element115;
public GpuUInt128 Element116;
public GpuUInt128 Element117;
public GpuUInt128 Element118;
public GpuUInt128 Element119;
public GpuUInt128 Element120;
public GpuUInt128 Element121;
public GpuUInt128 Element122;
public GpuUInt128 Element123;
public GpuUInt128 Element124;
public GpuUInt128 Element125;
public GpuUInt128 Element126;
public GpuUInt128 Element127;
public GpuUInt128 this[int index]
{
readonly get
{
return GetElement(index);
}
set
{
SetElement(index, value);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly GpuUInt128 GetElement(int index) => index switch
{
0 => Element0,
1 => Element1,
2 => Element2,
3 => Element3,
4 => Element4,
5 => Element5,
6 => Element6,
7 => Element7,
8 => Element8,
9 => Element9,
10 => Element10,
11 => Element11,
12 => Element12,
13 => Element13,
14 => Element14,
15 => Element15,
16 => Element16,
17 => Element17,
18 => Element18,
19 => Element19,
20 => Element20,
21 => Element21,
22 => Element22,
23 => Element23,
24 => Element24,
25 => Element25,
26 => Element26,
27 => Element27,
28 => Element28,
29 => Element29,
30 => Element30,
31 => Element31,
32 => Element32,
33 => Element33,
34 => Element34,
35 => Element35,
36 => Element36,
37 => Element37,
38 => Element38,
39 => Element39,
40 => Element40,
41 => Element41,
42 => Element42,
43 => Element43,
44 => Element44,
45 => Element45,
46 => Element46,
47 => Element47,
48 => Element48,
49 => Element49,
50 => Element50,
51 => Element51,
52 => Element52,
53 => Element53,
54 => Element54,
55 => Element55,
56 => Element56,
57 => Element57,
58 => Element58,
59 => Element59,
60 => Element60,
61 => Element61,
62 => Element62,
63 => Element63,
64 => Element64,
65 => Element65,
66 => Element66,
67 => Element67,
68 => Element68,
69 => Element69,
70 => Element70,
71 => Element71,
72 => Element72,
73 => Element73,
74 => Element74,
75 => Element75,
76 => Element76,
77 => Element77,
78 => Element78,
79 => Element79,
80 => Element80,
81 => Element81,
82 => Element82,
83 => Element83,
84 => Element84,
85 => Element85,
86 => Element86,
87 => Element87,
88 => Element88,
89 => Element89,
90 => Element90,
91 => Element91,
92 => Element92,
93 => Element93,
94 => Element94,
95 => Element95,
96 => Element96,
97 => Element97,
98 => Element98,
99 => Element99,
100 => Element100,
101 => Element101,
102 => Element102,
103 => Element103,
104 => Element104,
105 => Element105,
106 => Element106,
107 => Element107,
108 => Element108,
109 => Element109,
110 => Element110,
111 => Element111,
112 => Element112,
113 => Element113,
114 => Element114,
115 => Element115,
116 => Element116,
117 => Element117,
118 => Element118,
119 => Element119,
120 => Element120,
121 => Element121,
122 => Element122,
123 => Element123,
124 => Element124,
125 => Element125,
126 => Element126,
127 => Element127,
_ => Element0,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetElement(int index, GpuUInt128 value)
{
switch (index)
{
case 0:
Element0 = value;
return;
case 1:
Element1 = value;
return;
case 2:
Element2 = value;
return;
case 3:
Element3 = value;
return;
case 4:
Element4 = value;
return;
case 5:
Element5 = value;
return;
case 6:
Element6 = value;
return;
case 7:
Element7 = value;
return;
case 8:
Element8 = value;
return;
case 9:
Element9 = value;
return;
case 10:
Element10 = value;
return;
case 11:
Element11 = value;
return;
case 12:
Element12 = value;
return;
case 13:
Element13 = value;
return;
case 14:
Element14 = value;
return;
case 15:
Element15 = value;
return;
case 16:
Element16 = value;
return;
case 17:
Element17 = value;
return;
case 18:
Element18 = value;
return;
case 19:
Element19 = value;
return;
case 20:
Element20 = value;
return;
case 21:
Element21 = value;
return;
case 22:
Element22 = value;
return;
case 23:
Element23 = value;
return;
case 24:
Element24 = value;
return;
case 25:
Element25 = value;
return;
case 26:
Element26 = value;
return;
case 27:
Element27 = value;
return;
case 28:
Element28 = value;
return;
case 29:
Element29 = value;
return;
case 30:
Element30 = value;
return;
case 31:
Element31 = value;
return;
case 32:
Element32 = value;
return;
case 33:
Element33 = value;
return;
case 34:
Element34 = value;
return;
case 35:
Element35 = value;
return;
case 36:
Element36 = value;
return;
case 37:
Element37 = value;
return;
case 38:
Element38 = value;
return;
case 39:
Element39 = value;
return;
case 40:
Element40 = value;
return;
case 41:
Element41 = value;
return;
case 42:
Element42 = value;
return;
case 43:
Element43 = value;
return;
case 44:
Element44 = value;
return;
case 45:
Element45 = value;
return;
case 46:
Element46 = value;
return;
case 47:
Element47 = value;
return;
case 48:
Element48 = value;
return;
case 49:
Element49 = value;
return;
case 50:
Element50 = value;
return;
case 51:
Element51 = value;
return;
case 52:
Element52 = value;
return;
case 53:
Element53 = value;
return;
case 54:
Element54 = value;
return;
case 55:
Element55 = value;
return;
case 56:
Element56 = value;
return;
case 57:
Element57 = value;
return;
case 58:
Element58 = value;
return;
case 59:
Element59 = value;
return;
case 60:
Element60 = value;
return;
case 61:
Element61 = value;
return;
case 62:
Element62 = value;
return;
case 63:
Element63 = value;
return;
case 64:
Element64 = value;
return;
case 65:
Element65 = value;
return;
case 66:
Element66 = value;
return;
case 67:
Element67 = value;
return;
case 68:
Element68 = value;
return;
case 69:
Element69 = value;
return;
case 70:
Element70 = value;
return;
case 71:
Element71 = value;
return;
case 72:
Element72 = value;
return;
case 73:
Element73 = value;
return;
case 74:
Element74 = value;
return;
case 75:
Element75 = value;
return;
case 76:
Element76 = value;
return;
case 77:
Element77 = value;
return;
case 78:
Element78 = value;
return;
case 79:
Element79 = value;
return;
case 80:
Element80 = value;
return;
case 81:
Element81 = value;
return;
case 82:
Element82 = value;
return;
case 83:
Element83 = value;
return;
case 84:
Element84 = value;
return;
case 85:
Element85 = value;
return;
case 86:
Element86 = value;
return;
case 87:
Element87 = value;
return;
case 88:
Element88 = value;
return;
case 89:
Element89 = value;
return;
case 90:
Element90 = value;
return;
case 91:
Element91 = value;
return;
case 92:
Element92 = value;
return;
case 93:
Element93 = value;
return;
case 94:
Element94 = value;
return;
case 95:
Element95 = value;
return;
case 96:
Element96 = value;
return;
case 97:
Element97 = value;
return;
case 98:
Element98 = value;
return;
case 99:
Element99 = value;
return;
case 100:
Element100 = value;
return;
case 101:
Element101 = value;
return;
case 102:
Element102 = value;
return;
case 103:
Element103 = value;
return;
case 104:
Element104 = value;
return;
case 105:
Element105 = value;
return;
case 106:
Element106 = value;
return;
case 107:
Element107 = value;
return;
case 108:
Element108 = value;
return;
case 109:
Element109 = value;
return;
case 110:
Element110 = value;
return;
case 111:
Element111 = value;
return;
case 112:
Element112 = value;
return;
case 113:
Element113 = value;
return;
case 114:
Element114 = value;
return;
case 115:
Element115 = value;
return;
case 116:
Element116 = value;
return;
case 117:
Element117 = value;
return;
case 118:
Element118 = value;
return;
case 119:
Element119 = value;
return;
case 120:
Element120 = value;
return;
case 121:
Element121 = value;
return;
case 122:
Element122 = value;
return;
case 123:
Element123 = value;
return;
case 124:
Element124 = value;
return;
case 125:
Element125 = value;
return;
case 126:
Element126 = value;
return;
case 127:
Element127 = value;
return;
default:
// ILGPU kernels cannot throw exceptions, and callers guarantee the index range.
return;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetScalarBitLength(ulong value)
{
if (value == 0UL)
{
return 0;
}
return 64 - XMath.LeadingZeroCount(value);
}
public static readonly GpuUInt128 Zero = new();
public static readonly GpuUInt128 One = new(1UL);
@@ -423,7 +1109,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return new GpuUInt128(1UL);
}
Span<GpuUInt128> oddPowers = stackalloc GpuUInt128[Pow2WindowOddPowerCount];
Pow2OddPowerTable oddPowers = default;
GpuUInt128 power = new GpuUInt128(1UL);
int oddIndex = 0;
@@ -437,12 +1123,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
if ((bit & 1) != 0)
{
oddPowers[oddIndex++] = power;
oddPowers[oddIndex] = power;
oddIndex++;
}
}
GpuUInt128 result = new GpuUInt128(1UL);
int bitLength = BitOperations.Log2(exponent) + 1;
int bitLength = GetScalarBitLength(exponent);
int index = bitLength - 1;
while (index >= 0)
@@ -13,6 +13,9 @@ using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
namespace PerfectNumbers.Core.Gpu;
/// <summary>
/// Scans Mersenne divisors on the GPU for prime exponents p >= 31 using per-divisor Montgomery data.
/// </summary>
public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester
{
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
@@ -5,6 +5,9 @@ using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
/// <summary>
/// Provides GPU-accelerated divisibility checks for Mersenne numbers with prime exponents p >= 31.
/// </summary>
public sealed class MersenneNumberDivisorGpuTester
{
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<byte>>> _kernelCache = new();
@@ -1,9 +1,10 @@
using System.Buffers;
using System.Runtime.InteropServices;
using ILGPU.Runtime;
using System;
namespace PerfectNumbers.Core.Gpu;
/// <summary>
/// Uses GPU kernels to incrementally scan Mersenne candidates for prime exponents p >= 31.
/// </summary>
public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool useGpuOrder)
{
private readonly GpuKernelType _kernelType = kernelType;
@@ -11,94 +12,6 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
{
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize;
UInt128 kStart = 1UL;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastIsSeven ? (byte)1 : (byte)0;
var pow2Kernel = gpuLease.Pow2ModKernel; // TODO: Route this binding to the ProcessEightBitWindows kernel once the scalar helper lands so GPU incremental scans inherit the windowed speedup from GpuPow2ModBenchmarks.
var incKernel = gpuLease.IncrementalKernel;
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
GpuUInt128 twoPGpu = (GpuUInt128)twoP;
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod)
{
primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator);
}
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
UInt128 remaining;
int currentSize;
int i;
UInt128 q = UInt128.Zero;
try
{
while (kStart <= maxK && Volatile.Read(ref isPrime))
{
remaining = maxK - kStart + UInt128.One;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
UInt128 q0 = twoP * kStart + UInt128.One;
// TODO: Swap this multiply-and-add initialization for the divisor-cycle stepping helper so the GPU incremental path reuses cached offsets instead of recomputing twoP multiples every batch.
if (_kernelType == GpuKernelType.Pow2Mod)
{
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
pow2Kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul,
kernelArgs, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven,
primeViews.LastOnePow2, primeViews.LastSevenPow2);
}
else
{
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
incKernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul,
q0m10, q0m8, q0m3, q0m5, orderBuffer.View, smallCyclesView);
}
stream.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
if (_kernelType == GpuKernelType.Pow2Mod)
{
for (i = 0; i < currentSize; i++)
{
if (orders[i] != 0UL)
{
Volatile.Write(ref isPrime, false);
break;
}
}
}
else
{
q = twoP * kStart + UInt128.One;
// TODO: Replace this per-lane multiplication and the subsequent q += twoP stride with the shared divisor-cycle ladder so each iteration advances by cached cycle increments and automatically triggers on-demand GPU computations when the snapshot lacks the requested cycle.
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
{
if (orders[i] != 0UL && q.IsPrimeCandidate())
{
Volatile.Write(ref isPrime, false);
}
}
}
kStart += (UInt128)currentSize;
}
}
finally
{
ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose();
}
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
}
}
@@ -26,7 +26,7 @@ internal static partial class PrimeOrderGpuHeuristics
private static readonly ConcurrentDictionary<Accelerator, OrderKernelLauncher> OrderKernelCache = new();
private static readonly ConcurrentDictionary<Accelerator, SmallPrimeDeviceCache> SmallPrimeDeviceCaches = new();
private readonly struct OrderKernelConfig
public readonly struct OrderKernelConfig
{
public OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
{
@@ -48,7 +48,7 @@ internal static partial class PrimeOrderGpuHeuristics
public int Mode { get; }
}
private readonly struct OrderKernelBuffers
public readonly struct OrderKernelBuffers
{
public OrderKernelBuffers(
ArrayView1D<ulong, Stride1D.Dense> phiFactors,
@@ -125,10 +125,678 @@ internal static partial class PrimeOrderGpuHeuristics
private const int GpuSmallPrimeFactorSlots = 64;
[InlineArray(Pow2WindowOddPowerCount)]
private struct Pow2OddPowerTable
{
private GpuUInt128 _element0;
public GpuUInt128 Element0;
public GpuUInt128 Element1;
public GpuUInt128 Element2;
public GpuUInt128 Element3;
public GpuUInt128 Element4;
public GpuUInt128 Element5;
public GpuUInt128 Element6;
public GpuUInt128 Element7;
public GpuUInt128 Element8;
public GpuUInt128 Element9;
public GpuUInt128 Element10;
public GpuUInt128 Element11;
public GpuUInt128 Element12;
public GpuUInt128 Element13;
public GpuUInt128 Element14;
public GpuUInt128 Element15;
public GpuUInt128 Element16;
public GpuUInt128 Element17;
public GpuUInt128 Element18;
public GpuUInt128 Element19;
public GpuUInt128 Element20;
public GpuUInt128 Element21;
public GpuUInt128 Element22;
public GpuUInt128 Element23;
public GpuUInt128 Element24;
public GpuUInt128 Element25;
public GpuUInt128 Element26;
public GpuUInt128 Element27;
public GpuUInt128 Element28;
public GpuUInt128 Element29;
public GpuUInt128 Element30;
public GpuUInt128 Element31;
public GpuUInt128 Element32;
public GpuUInt128 Element33;
public GpuUInt128 Element34;
public GpuUInt128 Element35;
public GpuUInt128 Element36;
public GpuUInt128 Element37;
public GpuUInt128 Element38;
public GpuUInt128 Element39;
public GpuUInt128 Element40;
public GpuUInt128 Element41;
public GpuUInt128 Element42;
public GpuUInt128 Element43;
public GpuUInt128 Element44;
public GpuUInt128 Element45;
public GpuUInt128 Element46;
public GpuUInt128 Element47;
public GpuUInt128 Element48;
public GpuUInt128 Element49;
public GpuUInt128 Element50;
public GpuUInt128 Element51;
public GpuUInt128 Element52;
public GpuUInt128 Element53;
public GpuUInt128 Element54;
public GpuUInt128 Element55;
public GpuUInt128 Element56;
public GpuUInt128 Element57;
public GpuUInt128 Element58;
public GpuUInt128 Element59;
public GpuUInt128 Element60;
public GpuUInt128 Element61;
public GpuUInt128 Element62;
public GpuUInt128 Element63;
public GpuUInt128 Element64;
public GpuUInt128 Element65;
public GpuUInt128 Element66;
public GpuUInt128 Element67;
public GpuUInt128 Element68;
public GpuUInt128 Element69;
public GpuUInt128 Element70;
public GpuUInt128 Element71;
public GpuUInt128 Element72;
public GpuUInt128 Element73;
public GpuUInt128 Element74;
public GpuUInt128 Element75;
public GpuUInt128 Element76;
public GpuUInt128 Element77;
public GpuUInt128 Element78;
public GpuUInt128 Element79;
public GpuUInt128 Element80;
public GpuUInt128 Element81;
public GpuUInt128 Element82;
public GpuUInt128 Element83;
public GpuUInt128 Element84;
public GpuUInt128 Element85;
public GpuUInt128 Element86;
public GpuUInt128 Element87;
public GpuUInt128 Element88;
public GpuUInt128 Element89;
public GpuUInt128 Element90;
public GpuUInt128 Element91;
public GpuUInt128 Element92;
public GpuUInt128 Element93;
public GpuUInt128 Element94;
public GpuUInt128 Element95;
public GpuUInt128 Element96;
public GpuUInt128 Element97;
public GpuUInt128 Element98;
public GpuUInt128 Element99;
public GpuUInt128 Element100;
public GpuUInt128 Element101;
public GpuUInt128 Element102;
public GpuUInt128 Element103;
public GpuUInt128 Element104;
public GpuUInt128 Element105;
public GpuUInt128 Element106;
public GpuUInt128 Element107;
public GpuUInt128 Element108;
public GpuUInt128 Element109;
public GpuUInt128 Element110;
public GpuUInt128 Element111;
public GpuUInt128 Element112;
public GpuUInt128 Element113;
public GpuUInt128 Element114;
public GpuUInt128 Element115;
public GpuUInt128 Element116;
public GpuUInt128 Element117;
public GpuUInt128 Element118;
public GpuUInt128 Element119;
public GpuUInt128 Element120;
public GpuUInt128 Element121;
public GpuUInt128 Element122;
public GpuUInt128 Element123;
public GpuUInt128 Element124;
public GpuUInt128 Element125;
public GpuUInt128 Element126;
public GpuUInt128 Element127;
public GpuUInt128 this[int index]
{
readonly get
{
return GetElement(index);
}
set
{
SetElement(index, value);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly GpuUInt128 GetElement(int index) => index switch
{
0 => Element0,
1 => Element1,
2 => Element2,
3 => Element3,
4 => Element4,
5 => Element5,
6 => Element6,
7 => Element7,
8 => Element8,
9 => Element9,
10 => Element10,
11 => Element11,
12 => Element12,
13 => Element13,
14 => Element14,
15 => Element15,
16 => Element16,
17 => Element17,
18 => Element18,
19 => Element19,
20 => Element20,
21 => Element21,
22 => Element22,
23 => Element23,
24 => Element24,
25 => Element25,
26 => Element26,
27 => Element27,
28 => Element28,
29 => Element29,
30 => Element30,
31 => Element31,
32 => Element32,
33 => Element33,
34 => Element34,
35 => Element35,
36 => Element36,
37 => Element37,
38 => Element38,
39 => Element39,
40 => Element40,
41 => Element41,
42 => Element42,
43 => Element43,
44 => Element44,
45 => Element45,
46 => Element46,
47 => Element47,
48 => Element48,
49 => Element49,
50 => Element50,
51 => Element51,
52 => Element52,
53 => Element53,
54 => Element54,
55 => Element55,
56 => Element56,
57 => Element57,
58 => Element58,
59 => Element59,
60 => Element60,
61 => Element61,
62 => Element62,
63 => Element63,
64 => Element64,
65 => Element65,
66 => Element66,
67 => Element67,
68 => Element68,
69 => Element69,
70 => Element70,
71 => Element71,
72 => Element72,
73 => Element73,
74 => Element74,
75 => Element75,
76 => Element76,
77 => Element77,
78 => Element78,
79 => Element79,
80 => Element80,
81 => Element81,
82 => Element82,
83 => Element83,
84 => Element84,
85 => Element85,
86 => Element86,
87 => Element87,
88 => Element88,
89 => Element89,
90 => Element90,
91 => Element91,
92 => Element92,
93 => Element93,
94 => Element94,
95 => Element95,
96 => Element96,
97 => Element97,
98 => Element98,
99 => Element99,
100 => Element100,
101 => Element101,
102 => Element102,
103 => Element103,
104 => Element104,
105 => Element105,
106 => Element106,
107 => Element107,
108 => Element108,
109 => Element109,
110 => Element110,
111 => Element111,
112 => Element112,
113 => Element113,
114 => Element114,
115 => Element115,
116 => Element116,
117 => Element117,
118 => Element118,
119 => Element119,
120 => Element120,
121 => Element121,
122 => Element122,
123 => Element123,
124 => Element124,
125 => Element125,
126 => Element126,
127 => Element127,
_ => Element0,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetElement(int index, GpuUInt128 value)
{
switch (index)
{
case 0:
Element0 = value;
return;
case 1:
Element1 = value;
return;
case 2:
Element2 = value;
return;
case 3:
Element3 = value;
return;
case 4:
Element4 = value;
return;
case 5:
Element5 = value;
return;
case 6:
Element6 = value;
return;
case 7:
Element7 = value;
return;
case 8:
Element8 = value;
return;
case 9:
Element9 = value;
return;
case 10:
Element10 = value;
return;
case 11:
Element11 = value;
return;
case 12:
Element12 = value;
return;
case 13:
Element13 = value;
return;
case 14:
Element14 = value;
return;
case 15:
Element15 = value;
return;
case 16:
Element16 = value;
return;
case 17:
Element17 = value;
return;
case 18:
Element18 = value;
return;
case 19:
Element19 = value;
return;
case 20:
Element20 = value;
return;
case 21:
Element21 = value;
return;
case 22:
Element22 = value;
return;
case 23:
Element23 = value;
return;
case 24:
Element24 = value;
return;
case 25:
Element25 = value;
return;
case 26:
Element26 = value;
return;
case 27:
Element27 = value;
return;
case 28:
Element28 = value;
return;
case 29:
Element29 = value;
return;
case 30:
Element30 = value;
return;
case 31:
Element31 = value;
return;
case 32:
Element32 = value;
return;
case 33:
Element33 = value;
return;
case 34:
Element34 = value;
return;
case 35:
Element35 = value;
return;
case 36:
Element36 = value;
return;
case 37:
Element37 = value;
return;
case 38:
Element38 = value;
return;
case 39:
Element39 = value;
return;
case 40:
Element40 = value;
return;
case 41:
Element41 = value;
return;
case 42:
Element42 = value;
return;
case 43:
Element43 = value;
return;
case 44:
Element44 = value;
return;
case 45:
Element45 = value;
return;
case 46:
Element46 = value;
return;
case 47:
Element47 = value;
return;
case 48:
Element48 = value;
return;
case 49:
Element49 = value;
return;
case 50:
Element50 = value;
return;
case 51:
Element51 = value;
return;
case 52:
Element52 = value;
return;
case 53:
Element53 = value;
return;
case 54:
Element54 = value;
return;
case 55:
Element55 = value;
return;
case 56:
Element56 = value;
return;
case 57:
Element57 = value;
return;
case 58:
Element58 = value;
return;
case 59:
Element59 = value;
return;
case 60:
Element60 = value;
return;
case 61:
Element61 = value;
return;
case 62:
Element62 = value;
return;
case 63:
Element63 = value;
return;
case 64:
Element64 = value;
return;
case 65:
Element65 = value;
return;
case 66:
Element66 = value;
return;
case 67:
Element67 = value;
return;
case 68:
Element68 = value;
return;
case 69:
Element69 = value;
return;
case 70:
Element70 = value;
return;
case 71:
Element71 = value;
return;
case 72:
Element72 = value;
return;
case 73:
Element73 = value;
return;
case 74:
Element74 = value;
return;
case 75:
Element75 = value;
return;
case 76:
Element76 = value;
return;
case 77:
Element77 = value;
return;
case 78:
Element78 = value;
return;
case 79:
Element79 = value;
return;
case 80:
Element80 = value;
return;
case 81:
Element81 = value;
return;
case 82:
Element82 = value;
return;
case 83:
Element83 = value;
return;
case 84:
Element84 = value;
return;
case 85:
Element85 = value;
return;
case 86:
Element86 = value;
return;
case 87:
Element87 = value;
return;
case 88:
Element88 = value;
return;
case 89:
Element89 = value;
return;
case 90:
Element90 = value;
return;
case 91:
Element91 = value;
return;
case 92:
Element92 = value;
return;
case 93:
Element93 = value;
return;
case 94:
Element94 = value;
return;
case 95:
Element95 = value;
return;
case 96:
Element96 = value;
return;
case 97:
Element97 = value;
return;
case 98:
Element98 = value;
return;
case 99:
Element99 = value;
return;
case 100:
Element100 = value;
return;
case 101:
Element101 = value;
return;
case 102:
Element102 = value;
return;
case 103:
Element103 = value;
return;
case 104:
Element104 = value;
return;
case 105:
Element105 = value;
return;
case 106:
Element106 = value;
return;
case 107:
Element107 = value;
return;
case 108:
Element108 = value;
return;
case 109:
Element109 = value;
return;
case 110:
Element110 = value;
return;
case 111:
Element111 = value;
return;
case 112:
Element112 = value;
return;
case 113:
Element113 = value;
return;
case 114:
Element114 = value;
return;
case 115:
Element115 = value;
return;
case 116:
Element116 = value;
return;
case 117:
Element117 = value;
return;
case 118:
Element118 = value;
return;
case 119:
Element119 = value;
return;
case 120:
Element120 = value;
return;
case 121:
Element121 = value;
return;
case 122:
Element122 = value;
return;
case 123:
Element123 = value;
return;
case 124:
Element124 = value;
return;
case 125:
Element125 = value;
return;
case 126:
Element126 = value;
return;
case 127:
Element127 = value;
return;
default:
// ILGPU kernels cannot throw exceptions, and callers guarantee the index range.
return;
}
}
}
internal static ConcurrentDictionary<ulong, byte> OverflowRegistry => OverflowedPrimes;
@@ -1962,8 +2630,9 @@ internal static partial class PrimeOrderGpuHeuristics
baseValue.MulMod(baseValue, modulus);
for (int i = 1; i < oddPowerCount; i++)
{
oddPowers[i] = oddPowers[i - 1];
oddPowers[i].MulMod(baseValue, modulus);
GpuUInt128 ladderEntry = oddPowers[i - 1];
ladderEntry.MulMod(baseValue, modulus);
oddPowers[i] = ladderEntry;
}
}
@@ -17,6 +17,7 @@
<ItemGroup>
<InternalsVisibleTo Include="EvenPerfectBitScanner.Tests" />
<InternalsVisibleTo Include="PerfectNumbers.Core.Tests" />
<InternalsVisibleTo Include="ILGPURuntime" />
<InternalsVisibleTo Include="HeuristicMultiplyShiftTiming" />
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
</ItemGroup>
+14 -2
View File
@@ -1,6 +1,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ILGPU.Algorithms;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core;
@@ -679,7 +680,8 @@ public static class ULongExtensions
Element127 = value;
return;
default:
throw new ArgumentOutOfRangeException(nameof(index), index, "Pow2OddPowerTable index must be between 0 and 127.");
// ILGPU kernels cannot throw exceptions, and callers guarantee the index range.
return;
}
}
}
@@ -1011,7 +1013,7 @@ public static class ULongExtensions
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
}
int bitLength = BitOperations.Log2(exponent) + 1;
int bitLength = GetPortableBitLength(exponent);
int windowSize = GetWindowSize(bitLength);
int oddPowerCount = 1 << (windowSize - 1);
@@ -1093,6 +1095,16 @@ public static class ULongExtensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPortableBitLength(ulong value)
{
if (value == 0UL)
{
return 0;
}
return 64 - XMath.LeadingZeroCount(value);
}
private static int GetWindowSize(int bitLength)
{
if (bitLength <= 6)
+1
View File
@@ -1 +1,2 @@
- Reimplement the GPU cycle heuristics for MersenneNumberIncrementalGpuTester and re-enable Scan_handles_various_prime_exponents once the device path is fixed.
- Extend the GPU numeric type probe to cover proposed custom types such as GpuUInt256 and GpuUInt512 once their definitions are available so we can plan the wide-order heuristics on the device.