Add dedicated GPU residue evaluator for by-divisor deltas

This commit is contained in:
Marek Lemańczyk committed 2025-10-13 01:35:26 +02:00
1 parent 0902b18721
commit 41e6703926
2 files changed
+140 -53

No files matched your search

@@ -18,8 +18,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private ulong _configuredMaxPrime;
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
private MersenneNumberDivisorByDivisorGpuTester? _gpuDeltasTester;
private bool _gpuTesterConfigured;
public int BatchSize
@@ -29,14 +27,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{
int sanitized = Math.Max(1, value);
_batchSize = sanitized;
lock (_sync)
{
if (_gpuDeltasTester is not null)
{
_gpuDeltasTester.GpuBatchSize = sanitized;
}
}
}
}
@@ -60,7 +50,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
_isConfigured = true;
_gpuTesterConfigured = false;
}
}
@@ -167,31 +156,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
}
internal IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateGpuDeltasSession()
{
MersenneNumberDivisorByDivisorGpuTester tester;
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
tester = _gpuDeltasTester ??= new MersenneNumberDivisorByDivisorGpuTester();
if (!_gpuTesterConfigured)
{
tester.ConfigureFromMaxPrime(_configuredMaxPrime);
_gpuTesterConfigured = true;
}
tester.GpuBatchSize = _batchSize;
}
return tester.CreateDivisorSession();
}
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{
lock (_sync)
@@ -514,7 +478,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private ulong[]? _cycleRemainders;
private ulong[]? _montgomeryDeltas;
private int _bufferCapacity;
private IMersenneNumberDivisorByDivisorTester.IDivisorScanSession? _gpuSession;
private MersenneNumberDivisorResidueGpuEvaluator? _gpuResidueEvaluator;
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
{
@@ -572,13 +536,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
}
if (_owner._deltasDevice == ByDivisorDeltasDevice.Gpu)
{
EnsureGpuSession();
_gpuSession!.CheckDivisor(divisor, cachedData, divisorCycle, primes, hits);
return;
}
EnsureCapacity(length);
Span<ulong> deltaSpan = _primeDeltas!.AsSpan(0, length);
@@ -587,11 +544,27 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
Span<ulong> remainderSpan = _cycleRemainders!.AsSpan(0, length);
ComputeCycleRemainders(divisorCycle, primes, deltaSpan, remainderSpan);
ulong montgomeryOne = cachedData.MontgomeryOne;
if (_owner._deltasDevice == ByDivisorDeltasDevice.Gpu)
{
EnsureGpuEvaluator();
Span<ulong> residueSpan = _montgomeryDeltas!.AsSpan(0, length);
// Reuse the Montgomery delta buffer to hold the final residues returned by the GPU evaluator.
_gpuResidueEvaluator!.ComputeResidues(primes, cachedData, residueSpan);
for (int i = 0; i < length; i++)
{
hits[i] = remainderSpan[i] == 0UL && residueSpan[i] == montgomeryOne ? (byte)1 : (byte)0;
}
return;
}
Span<ulong> montgomerySpan = _montgomeryDeltas!.AsSpan(0, length);
ComputeMontgomeryDeltas(cachedData, primes, deltaSpan, montgomerySpan);
ulong nPrime = cachedData.NPrime;
ulong montgomeryOne = cachedData.MontgomeryOne;
ulong currentMontgomery = montgomerySpan[0];
hits[0] = remainderSpan[0] == 0UL && currentMontgomery == montgomeryOne ? (byte)1 : (byte)0;
@@ -603,23 +576,23 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
}
private void EnsureGpuSession()
private void EnsureGpuEvaluator()
{
if (_gpuSession is null)
if (_gpuResidueEvaluator is null)
{
_gpuSession = _owner.CreateGpuDeltasSession();
_gpuResidueEvaluator = new MersenneNumberDivisorResidueGpuEvaluator(_owner._batchSize);
}
}
private void ReleaseGpuSession()
private void ReleaseGpuEvaluator()
{
if (_gpuSession is null)
if (_gpuResidueEvaluator is null)
{
return;
}
_gpuSession.Dispose();
_gpuSession = null;
_gpuResidueEvaluator.Dispose();
_gpuResidueEvaluator = null;
}
private void EnsureCapacity(int requiredLength)
@@ -767,7 +740,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
_disposed = true;
ReleaseGpuSession();
ReleaseGpuEvaluator();
_owner.ReturnSession(this);
}
}
@@ -0,0 +1,114 @@
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using PerfectNumbers.Core;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
{
private readonly GpuContextPool.GpuContextLease _lease;
private readonly Accelerator _accelerator;
private readonly Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> _kernel;
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentBuffer;
private MemoryBuffer1D<ulong, Stride1D.Dense> _resultBuffer;
private ulong[] _hostBuffer;
private int _capacity;
private bool _disposed;
internal MersenneNumberDivisorResidueGpuEvaluator(int batchSize)
{
_lease = GpuContextPool.RentPreferred(preferCpu: false);
_accelerator = _lease.Accelerator;
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeResiduesKernel);
_capacity = Math.Max(1, batchSize);
_exponentBuffer = _accelerator.Allocate1D<ulong>(_capacity);
_resultBuffer = _accelerator.Allocate1D<ulong>(_capacity);
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_exponentBuffer.Dispose();
_resultBuffer.Dispose();
ArrayPool<ulong>.Shared.Return(_hostBuffer, clearArray: false);
_lease.Dispose();
}
internal void ComputeResidues(ReadOnlySpan<ulong> exponents, in MontgomeryDivisorData divisorData, Span<ulong> destination)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MersenneNumberDivisorResidueGpuEvaluator));
}
int length = exponents.Length;
if (length == 0)
{
return;
}
EnsureCapacity(length);
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length);
exponents.CopyTo(hostSpan);
ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentBuffer.View.SubView(0, length);
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length);
ArrayView1D<ulong, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, length);
_kernel(length, divisorData, exponentView, resultView);
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostSpan), length);
hostSpan.CopyTo(destination);
}
private void EnsureCapacity(int required)
{
if (required <= _capacity)
{
return;
}
int newCapacity = _capacity;
if (newCapacity == 0)
{
newCapacity = 1;
}
while (newCapacity < required)
{
newCapacity <<= 1;
}
_exponentBuffer.Dispose();
_resultBuffer.Dispose();
ArrayPool<ulong>.Shared.Return(_hostBuffer, clearArray: false);
_capacity = newCapacity;
_exponentBuffer = _accelerator.Allocate1D<ulong>(_capacity);
_resultBuffer = _accelerator.Allocate1D<ulong>(_capacity);
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
}
private static void ComputeResiduesKernel(Index1D index, MontgomeryDivisorData divisorData, ArrayView<ulong> exponents, ArrayView<ulong> results)
{
int idx = index;
int length = (int)exponents.Length;
if (idx >= length)
{
return;
}
ulong exponent = exponents[idx];
results[idx] = exponent.Pow2MontgomeryModWindowedGpu(divisorData, keepMontgomery: true);
}
}