Refine GPU residue utilities and add positive divisor tests

This commit is contained in:
Marek Lemańczyk committed 2025-09-13 16:47:38 +02:00
1 parent 515a1bb342
commit 58eb65ca9e
7 files changed
+224 -121

No files matched your search

+107 -36
View File
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using static PerfectNumbers.Core.Gpu.GpuContextPool;
@@ -29,7 +30,7 @@ public sealed class KernelContainer
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>? Incremental;
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>? Pow2Mod;
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>? IncrementalOrder;
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
@@ -37,6 +38,8 @@ public sealed class KernelContainer
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2;
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
{
@@ -77,17 +80,17 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
}
}
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>> Pow2ModKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>(Pow2ModKernelScan));
}
}
}
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan));
}
}
}
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>> IncrementalKernel
{
@@ -274,49 +277,45 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
}
private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders,
ArrayView1D<uint, Stride1D.Dense> smallCycles)
ResidueAutomatonArgs ra, ArrayView<ulong> orders,
ArrayView1D<uint, Stride1D.Dense> smallCycles, ArrayView1D<uint, Stride1D.Dense> smallPrimes,
ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2)
{
ulong idx = (ulong)index.X;
ulong step10 = ((exponent % 10UL) << 1) % 10UL;
ulong r10 = q0m10 + (step10 * idx) % 10UL; r10 -= (r10 >= 10UL) ? 10UL : 0UL;
bool shouldCheck = r10 != 5UL;
if (shouldCheck)
{
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
ulong r8 = (q0m8 + ((step8 * idx) & 7UL)) & 7UL;
if (r8 != 1UL && r8 != 7UL)
{
shouldCheck = false;
}
ulong r10 = ra.Q0M10 + (ra.Step10 * idx) % 10UL; r10 -= (r10 >= 10UL) ? 10UL : 0UL;
bool shouldCheck = r10 != 5UL;
if (shouldCheck)
{
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
if (r8 != 1UL && r8 != 7UL)
{
shouldCheck = false;
}
else
{
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
ulong r3 = q0m3 + (step3 * (idx % 3UL)); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
ulong r3 = ra.Q0M3 + (ra.Step3 * (idx % 3UL)); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
if (r3 == 0UL)
{
shouldCheck = false;
}
else
{
ulong step5Loc = ((exponent % 5UL) << 1) % 5UL;
ulong r5 = q0m5 + (step5Loc * (idx % 5UL)); if (r5 >= 5UL) r5 -= 5UL;
ulong r5 = ra.Q0M5 + (ra.Step5 * (idx % 5UL)); if (r5 >= 5UL) r5 -= 5UL;
if (r5 == 0UL)
{
shouldCheck = false;
}
}
}
}
if (!shouldCheck)
{
orders[index] = 0UL;
return;
}
}
if (!shouldCheck)
{
orders[index] = 0UL;
return;
}
GpuUInt128 k = kStart + (GpuUInt128)idx;
GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One;
// Small-cycles in-kernel early rejection from device table
if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1))
{
uint cyc = smallCycles[(int)q.Low];
@@ -332,9 +331,54 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
return;
}
int primesLen = (int)smallPrimes.Length;
for (int i = 0; i < primesLen; i++)
{
ulong square = smallPrimesPow2[i];
if (new GpuUInt128(0UL, square) > q)
{
break;
}
ulong prime = smallPrimes[i];
if (Mod128By64(q, prime) == 0UL)
{
orders[index] = 0UL;
return;
}
}
orders[index] = exponent;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mod128By64(GpuUInt128 value, ulong modulus)
{
ulong rem = 0UL;
ulong part = value.High;
for (int i = 0; i < 64; i++)
{
rem = (rem << 1) | (part >> 63);
if (rem >= modulus)
{
rem -= modulus;
}
part <<= 1;
}
part = value.Low;
for (int i = 0; i < 64; i++)
{
rem = (rem << 1) | (part >> 63);
if (rem >= modulus)
{
rem -= modulus;
}
part <<= 1;
}
return rem;
}
// Device-friendly small cycle length for 2 mod divisor
private static ulong CalculateCycleLengthSmall(ulong divisor)
{
@@ -527,6 +571,33 @@ public class GpuKernelPool
}
}
public static (ArrayView1D<uint, Stride1D.Dense> primes, ArrayView1D<ulong, Stride1D.Dense> primesPow2) EnsureSmallPrimesOnDevice(Accelerator accelerator)
{
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimes is { } p && kernels.SmallPrimesPow2 is { } p2)
{
return (p.View, p2.View);
}
lock (kernels)
{
if (kernels.SmallPrimes is { } existing && kernels.SmallPrimesPow2 is { } existingPow2)
{
return (existing.View, existingPow2.View);
}
var hostPrimes = PrimesGenerator.SmallPrimes;
var hostPow2 = PrimesGenerator.SmallPrimesPow2;
var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length);
devicePrimes.View.CopyFromCPU(hostPrimes);
var devicePow2 = accelerator.Allocate1D<ulong>(hostPow2.Length);
devicePow2.View.CopyFromCPU(hostPow2);
kernels.SmallPrimes = devicePrimes;
kernels.SmallPrimesPow2 = devicePow2;
return (devicePrimes.View, devicePow2.View);
}
}
public static GpuKernelLease GetKernel(bool useGpuOrder)
{
var limiter = GpuPrimeWorkLimiter.Acquire();