Add GPU options for by-divisor Montgomery data

This commit is contained in:
Marek Lemańczyk committed 2025-10-13 01:35:46 +02:00
1 parent ea29035c55
commit 483584b5d4
5 files changed
+449 -77

No files matched your search

@@ -0,0 +1,179 @@
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using PerfectNumbers.Core;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
{
private readonly GpuContextPool.GpuContextLease _lease;
private readonly Accelerator _accelerator;
private readonly Action<Index1D, ArrayView<ulong>, ArrayView<MontgomeryDivisorData>> _kernel;
private MemoryBuffer1D<ulong, Stride1D.Dense> _modulusBuffer;
private MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> _resultBuffer;
private ulong[] _hostModulusBuffer;
private MontgomeryDivisorData[] _hostResultBuffer;
private int _capacity;
private bool _disposed;
internal MersenneNumberDivisorMontgomeryGpuBuilder(int batchSize)
{
_lease = GpuContextPool.RentPreferred(preferCpu: false);
_accelerator = _lease.Accelerator;
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<MontgomeryDivisorData>>(BuildKernel);
_capacity = Math.Max(1, batchSize);
_modulusBuffer = _accelerator.Allocate1D<ulong>(_capacity);
_resultBuffer = _accelerator.Allocate1D<MontgomeryDivisorData>(_capacity);
_hostModulusBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
_hostResultBuffer = ArrayPool<MontgomeryDivisorData>.Shared.Rent(_capacity);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_modulusBuffer.Dispose();
_resultBuffer.Dispose();
ArrayPool<ulong>.Shared.Return(_hostModulusBuffer, clearArray: false);
ArrayPool<MontgomeryDivisorData>.Shared.Return(_hostResultBuffer, clearArray: false);
_lease.Dispose();
}
internal void Build(ReadOnlySpan<ulong> moduli, Span<MontgomeryDivisorData> destination)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MersenneNumberDivisorMontgomeryGpuBuilder));
}
int length = moduli.Length;
if (length == 0)
{
return;
}
EnsureCapacity(length);
Span<ulong> hostModulusSpan = _hostModulusBuffer.AsSpan(0, length);
moduli.CopyTo(hostModulusSpan);
ArrayView1D<ulong, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, length);
modulusView.CopyFromCPU(ref MemoryMarshal.GetReference(hostModulusSpan), length);
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, length);
_kernel(length, modulusView, resultView);
Span<MontgomeryDivisorData> hostResultSpan = _hostResultBuffer.AsSpan(0, length);
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostResultSpan), length);
hostResultSpan.CopyTo(destination);
}
private void EnsureCapacity(int required)
{
if (required <= _capacity)
{
return;
}
int newCapacity = _capacity == 0 ? 1 : _capacity;
while (newCapacity < required)
{
newCapacity <<= 1;
}
_modulusBuffer.Dispose();
_resultBuffer.Dispose();
ArrayPool<ulong>.Shared.Return(_hostModulusBuffer, clearArray: false);
ArrayPool<MontgomeryDivisorData>.Shared.Return(_hostResultBuffer, clearArray: false);
_capacity = newCapacity;
_modulusBuffer = _accelerator.Allocate1D<ulong>(_capacity);
_resultBuffer = _accelerator.Allocate1D<MontgomeryDivisorData>(_capacity);
_hostModulusBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
_hostResultBuffer = ArrayPool<MontgomeryDivisorData>.Shared.Rent(_capacity);
}
private static void BuildKernel(Index1D index, ArrayView<ulong> moduli, ArrayView<MontgomeryDivisorData> destination)
{
int idx = index;
if (idx >= moduli.Length)
{
return;
}
ulong modulus = moduli[idx];
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
destination[idx] = default;
return;
}
ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue64(modulus);
ulong montgomeryTwo = montgomeryOne.MulMod64GpuCompatible(2UL, modulus);
ulong montgomeryTwoSquared = MontgomeryMultiplyGpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
destination[idx] = new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
private static ulong ComputeMontgomeryResidue64(ulong modulus)
{
ulong remainder = ulong.MaxValue % modulus;
remainder++;
if (remainder >= modulus)
{
remainder -= modulus;
}
return remainder;
}
private static ulong MontgomeryMultiplyGpu(ulong a, ulong b, ulong modulus, ulong nPrime)
{
GpuUInt128 product = new(a);
product.Mul64(new GpuUInt128(b));
ulong tLow = product.Low;
ulong tHigh = product.High;
ulong m = unchecked(tLow * nPrime);
GpuUInt128 mProduct = new(m);
mProduct.Mul64(new GpuUInt128(modulus));
ulong mLow = mProduct.Low;
ulong mHigh = mProduct.High;
ulong carry = unchecked((tLow + mLow) < tLow ? 1UL : 0UL);
ulong result = tHigh + mHigh + carry;
if (result >= modulus)
{
result -= modulus;
}
return result;
}
private static ulong ComputeMontgomeryNPrime(ulong modulus)
{
ulong inv = modulus;
inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv);
return unchecked(0UL - inv);
}
}
@@ -0,0 +1,135 @@
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
{
private readonly GpuContextPool.GpuContextLease _lease;
private readonly Accelerator _accelerator;
private readonly Action<Index1D, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> _kernel;
private MemoryBuffer1D<byte, Stride1D.Dense> _remainderBuffer;
private MemoryBuffer1D<byte, Stride1D.Dense> _stepBuffer;
private MemoryBuffer1D<byte, Stride1D.Dense> _modulusBuffer;
private MemoryBuffer1D<byte, Stride1D.Dense> _resultBuffer;
private byte[] _hostRemainders;
private byte[] _hostSteps;
private byte[] _hostModuli;
private readonly int _length;
private int _capacity;
private bool _disposed;
internal MersenneNumberDivisorRemainderGpuStepper(ReadOnlySpan<byte> steps, ReadOnlySpan<byte> moduli)
{
if (steps.Length != moduli.Length)
{
throw new ArgumentException("Steps and moduli must have the same length.");
}
_lease = GpuContextPool.RentPreferred(preferCpu: false);
_accelerator = _lease.Accelerator;
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>>(AdvanceRemaindersKernel);
_length = steps.Length;
_capacity = Math.Max(1, _length);
_remainderBuffer = _accelerator.Allocate1D<byte>(_capacity);
_stepBuffer = _accelerator.Allocate1D<byte>(_capacity);
_modulusBuffer = _accelerator.Allocate1D<byte>(_capacity);
_resultBuffer = _accelerator.Allocate1D<byte>(_capacity);
_hostRemainders = ArrayPool<byte>.Shared.Rent(_capacity);
_hostSteps = ArrayPool<byte>.Shared.Rent(_capacity);
_hostModuli = ArrayPool<byte>.Shared.Rent(_capacity);
steps.CopyTo(_hostSteps.AsSpan(0, _length));
moduli.CopyTo(_hostModuli.AsSpan(0, _length));
ArrayView1D<byte, Stride1D.Dense> stepView = _stepBuffer.View.SubView(0, _length);
stepView.CopyFromCPU(ref MemoryMarshal.GetReference(_hostSteps.AsSpan(0, _length)), _length);
ArrayView1D<byte, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, _length);
modulusView.CopyFromCPU(ref MemoryMarshal.GetReference(_hostModuli.AsSpan(0, _length)), _length);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_remainderBuffer.Dispose();
_stepBuffer.Dispose();
_modulusBuffer.Dispose();
_resultBuffer.Dispose();
ArrayPool<byte>.Shared.Return(_hostRemainders, clearArray: false);
ArrayPool<byte>.Shared.Return(_hostSteps, clearArray: false);
ArrayPool<byte>.Shared.Return(_hostModuli, clearArray: false);
_lease.Dispose();
}
internal void Advance(int advanceCount, Span<byte> remainders)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper));
}
if (advanceCount <= 0 || remainders.Length == 0)
{
return;
}
if (remainders.Length != _length)
{
throw new ArgumentException("Remainder buffer length must match the configured length.");
}
Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length);
remainders.CopyTo(hostRemainderSpan);
ArrayView1D<byte, Stride1D.Dense> remainderView = _remainderBuffer.View.SubView(0, _length);
remainderView.CopyFromCPU(ref MemoryMarshal.GetReference(hostRemainderSpan), _length);
ArrayView1D<byte, Stride1D.Dense> stepView = _stepBuffer.View.SubView(0, _length);
ArrayView1D<byte, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, _length);
ArrayView1D<byte, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, _length);
_kernel(_length, advanceCount, remainderView, stepView, modulusView, resultView);
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostRemainderSpan), _length);
hostRemainderSpan.CopyTo(remainders);
}
private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> moduli, ArrayView<byte> results)
{
int idx = index;
if (idx >= remainders.Length)
{
return;
}
int modulus = moduli[idx];
if (modulus == 0)
{
results[idx] = remainders[idx];
return;
}
int remainder = remainders[idx] % modulus;
int step = steps[idx] % modulus;
long delta = (long)step * advanceCount;
int adjusted = (int)((remainder + delta) % modulus);
if (adjusted < 0)
{
adjusted += modulus;
}
results[idx] = (byte)adjusted;
}
}