Fuse GPU hit detection with divisor evaluation

This commit is contained in:
Marek Lemańczyk committed 2025-10-24 01:23:31 +02:00
1 parent 40d3b4b3ac
commit 1075ab1ea1
2 files changed
+27 -66

No files matched your search

@@ -8,15 +8,27 @@ namespace PerfectNumbers.Core.Gpu;
internal static class DivisorByDivisorKernels internal static class DivisorByDivisorKernels
{ {
public static void CheckKernel(Index1D index, ArrayView<MontgomeryDivisorData> divisors, ArrayView<ulong> exponents, ArrayView<byte> hits) public static void CheckKernel(
Index1D index,
ArrayView<MontgomeryDivisorData> divisors,
ArrayView<ulong> exponents,
ArrayView<byte> hits,
ArrayView<int> firstHit)
{ {
MontgomeryDivisorData divisor = divisors[index]; int globalIndex = index;
MontgomeryDivisorData divisor = divisors[globalIndex];
ulong modulus = divisor.Modulus; ulong modulus = divisor.Modulus;
// The GPU by-divisor pipeline only materializes odd moduli greater than one (q = 2kp + 1), // The GPU by-divisor pipeline only materializes odd moduli greater than one (q = 2kp + 1),
// so the defensive guard for invalid values stays disabled here to keep the kernel branch-free. // so the defensive guard for invalid values stays disabled here to keep the kernel branch-free.
ulong exponent = exponents[index]; ulong exponent = exponents[globalIndex];
ulong montgomeryResult = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent); ulong montgomeryResult = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent);
hits[index] = montgomeryResult == 1UL ? (byte)1 : (byte)0; byte hit = montgomeryResult == 1UL ? (byte)1 : (byte)0;
hits[globalIndex] = hit;
if (hit != 0)
{
Atomic.Min(ref firstHit[0], globalIndex);
}
} }
public static void EvaluateDivisorWithStepperKernel( public static void EvaluateDivisorWithStepperKernel(
@@ -173,25 +185,6 @@ internal static class DivisorByDivisorKernels
} }
} }
public static void PublishFirstHitKernel(
Index1D index,
ArrayView<byte> hits,
ArrayView<int> firstHit)
{
int globalIndex = index;
// EvenPerfectBitScanner launches this auto-grouped kernel with an extent matching the hit view length,
// so the defensive guard stays commented out to keep the hot path branch-free.
// int length = (int)hits.Length;
// if (globalIndex >= length)
// {
// return;
// }
byte hitValue = hits[globalIndex];
int candidate = hitValue != 0 ? globalIndex : int.MaxValue;
Atomic.Min(ref firstHit[0], candidate);
}
public static void ConvertMontgomeryResultsToHitsKernel( public static void ConvertMontgomeryResultsToHitsKernel(
Index1D index, Index1D index,
ArrayView<ulong> results, ArrayView<ulong> results,
@@ -23,16 +23,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private ulong _divisorLimit; private ulong _divisorLimit;
// private bool _isConfigured; // private bool _isConfigured;
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>> _kernelCache = new(); private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>> _kernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>>> _stepperKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>>> _stepperKernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, Action<KernelConfig, Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte>> _filteredCandidateKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary<Accelerator, Action<KernelConfig, Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte>> _filteredCandidateKernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<byte>, ArrayView<int>>> _hitDetectionKernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new(); private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new(); private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
private Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> GetKernel(Accelerator accelerator) => private Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator) =>
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>(DivisorByDivisorKernels.CheckKernel)); _kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel));
private Action<Index1D, MontgomeryDivisorData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>> GetStepperKernel(Accelerator accelerator) => private Action<Index1D, MontgomeryDivisorData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>> GetStepperKernel(Accelerator accelerator) =>
_stepperKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>>(DivisorByDivisorKernels.EvaluateDivisorWithStepperKernel)); _stepperKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>>(DivisorByDivisorKernels.EvaluateDivisorWithStepperKernel));
@@ -42,11 +41,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
accelerator, accelerator,
acc => acc.LoadStreamKernel<Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte>(DivisorByDivisorKernels.GenerateFilteredCandidatesKernel)); acc => acc.LoadStreamKernel<Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte>(DivisorByDivisorKernels.GenerateFilteredCandidatesKernel));
private Action<Index1D, ArrayView<byte>, ArrayView<int>> GetHitDetectionKernel(Accelerator accelerator) =>
_hitDetectionKernelCache.GetOrAdd(
accelerator,
acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.PublishFirstHitKernel));
public int GpuBatchSize public int GpuBatchSize
{ {
get => _gpuBatchSize; get => _gpuBatchSize;
@@ -126,7 +120,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
resources.NextStartBuffer, resources.NextStartBuffer,
resources.FilterCountBuffer, resources.FilterCountBuffer,
resources.FilteredCandidateKernel, resources.FilteredCandidateKernel,
resources.HitDetectionKernel,
resources.Divisors, resources.Divisors,
resources.Exponents, resources.Exponents,
resources.DivisorData, resources.DivisorData,
@@ -183,7 +176,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
Accelerator accelerator, Accelerator accelerator,
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel, Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel,
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer, MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer, MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer, MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
@@ -192,7 +185,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
MemoryBuffer1D<GpuUInt128, Stride1D.Dense> nextStartBuffer, MemoryBuffer1D<GpuUInt128, Stride1D.Dense> nextStartBuffer,
MemoryBuffer1D<int, Stride1D.Dense> filterCountBuffer, MemoryBuffer1D<int, Stride1D.Dense> filterCountBuffer,
Action<KernelConfig, Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte> filteredCandidateKernel, Action<KernelConfig, Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte> filteredCandidateKernel,
Action<Index1D, ArrayView<byte>, ArrayView<int>> hitDetectionKernel,
in ulong[] divisors, in ulong[] divisors,
ulong[] exponents, ulong[] exponents,
MontgomeryDivisorData[] divisorData, MontgomeryDivisorData[] divisorData,
@@ -336,9 +328,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount); divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount);
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), admissibleCount); exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), admissibleCount);
kernel(admissibleCount, divisorView, exponentView, hitsView); int sentinel = int.MaxValue;
hitIndexView.SubView(0, 1).CopyFromCPU(ref sentinel, 1);
int hitIndex = FindFirstHitOnGpu(admissibleCount, hitsView, hitIndexView, hitDetectionKernel); kernel(admissibleCount, divisorView, exponentView, hitsView, hitIndexView);
int firstHit = sentinel;
hitIndexView.SubView(0, 1).CopyToCPU(ref firstHit, 1);
int hitIndex = firstHit >= admissibleCount ? -1 : firstHit;
bool hitFound = hitIndex >= 0; bool hitFound = hitIndex >= 0;
composite = hitFound; composite = hitFound;
int lastIndex = admissibleCount - 1; int lastIndex = admissibleCount - 1;
@@ -352,9 +349,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GenerateFilteredCandidatesOnGpu( private static int GenerateFilteredCandidatesOnGpu(
int count, int count,
@@ -390,29 +384,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return filteredCount; return filteredCount;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FindFirstHitOnGpu(
int count,
ArrayView1D<byte, Stride1D.Dense> hitsView,
ArrayView1D<int, Stride1D.Dense> hitIndexView,
Action<Index1D, ArrayView<byte>, ArrayView<int>> hitKernel)
{
// EvenPerfectBitScanner only calls this helper with positive counts,
// so the guard stays commented out to keep the hot path branch-free.
// if (count <= 0)
// {
// return -1;
// }
int sentinel = int.MaxValue;
hitIndexView.SubView(0, 1).CopyFromCPU(ref sentinel, 1);
hitKernel(count, hitsView, hitIndexView);
int firstHit = sentinel;
hitIndexView.SubView(0, 1).CopyToCPU(ref firstHit, 1);
return firstHit >= count ? -1 : firstHit;
}
@@ -660,7 +631,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
DivisorData = ThreadStaticPools.MontgomeryDivisorDataPool.Rent(actualCapacity); DivisorData = ThreadStaticPools.MontgomeryDivisorDataPool.Rent(actualCapacity);
CycleLengths = ulongPool.Rent(cycleCapacity); CycleLengths = ulongPool.Rent(cycleCapacity);
FilteredCandidateKernel = owner.GetFilteredCandidateKernel(accelerator); FilteredCandidateKernel = owner.GetFilteredCandidateKernel(accelerator);
HitDetectionKernel = owner.GetHitDetectionKernel(accelerator);
Capacity = actualCapacity; Capacity = actualCapacity;
CycleCapacity = cycleCapacity; CycleCapacity = cycleCapacity;
} }
@@ -689,8 +659,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
internal readonly Action<KernelConfig, Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte> FilteredCandidateKernel; internal readonly Action<KernelConfig, Index1D, ArrayView<ulong>, ArrayView<int>, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128, byte> FilteredCandidateKernel;
internal readonly Action<Index1D, ArrayView<byte>, ArrayView<int>> HitDetectionKernel;
internal readonly int Capacity; internal readonly int Capacity;
internal readonly int CycleCapacity; internal readonly int CycleCapacity;