Files
even-perfect-numbers-scanner/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs
T

674 lines
23 KiB
C#

using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using ILGPU;
using ILGPU.Runtime;
using ILGPU.Runtime.OpenCL;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu;
internal enum GpuPow2ModStatus
{
Success,
Overflow,
Unavailable,
}
internal static partial class PrimeOrderGpuHeuristics
{
internal static void PreloadStaticTables(AcceleratorStream stream)
{
// _ = GetSmallPrimeDeviceCache(stream);
}
private static readonly ConcurrentDictionary<ulong, byte> OverflowedPrimes = new();
private static readonly ConcurrentDictionary<UInt128, byte> OverflowedPrimesWide = new();
[ThreadStatic]
private static Kernel[]? _pow2ModKernel;
[ThreadStatic]
private static Kernel[]? _partialFactorKernel;
[ThreadStatic]
private static Kernel[]? _orderKernel;
public readonly struct OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
{
public readonly ulong PreviousOrder = previousOrder;
public readonly byte HasPreviousOrder = hasPreviousOrder;
public readonly uint SmallFactorLimit = smallFactorLimit;
public readonly int MaxPowChecks = maxPowChecks;
public readonly int Mode = mode;
}
public readonly struct OrderKernelBuffers(
ArrayView1D<ulong, Stride1D.Dense> phiFactors,
ArrayView1D<int, Stride1D.Dense> phiExponents,
ArrayView1D<ulong, Stride1D.Dense> workFactors,
ArrayView1D<int, Stride1D.Dense> workExponents,
ArrayView1D<ulong, Stride1D.Dense> candidates,
ArrayView1D<int, Stride1D.Dense> stackIndex,
ArrayView1D<int, Stride1D.Dense> stackExponent,
ArrayView1D<ulong, Stride1D.Dense> stackProduct,
ArrayView1D<ulong, Stride1D.Dense> result,
ArrayView1D<byte, Stride1D.Dense> status)
{
public readonly ArrayView1D<ulong, Stride1D.Dense> PhiFactors = phiFactors;
public readonly ArrayView1D<int, Stride1D.Dense> PhiExponents = phiExponents;
public readonly ArrayView1D<ulong, Stride1D.Dense> WorkFactors = workFactors;
public readonly ArrayView1D<int, Stride1D.Dense> WorkExponents = workExponents;
public readonly ArrayView1D<ulong, Stride1D.Dense> Candidates = candidates;
public readonly ArrayView1D<int, Stride1D.Dense> StackIndex = stackIndex;
public readonly ArrayView1D<int, Stride1D.Dense> StackExponent = stackExponent;
public readonly ArrayView1D<ulong, Stride1D.Dense> StackProduct = stackProduct;
public readonly ArrayView1D<ulong, Stride1D.Dense> Result = result;
public readonly ArrayView1D<byte, Stride1D.Dense> Status = status;
}
private const int WideStackThreshold = 12;
private static PrimeOrderGpuCapability s_capability = PrimeOrderGpuCapability.Default;
private const int Pow2WindowSizeBits = 8;
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSizeBits - 1);
private const ulong Pow2WindowFallbackThreshold = 32UL;
private const int HeuristicCandidateLimit = 512;
private const int HeuristicStackCapacity = 256;
private const int GpuSmallPrimeFactorSlots = 64;
internal static ConcurrentDictionary<ulong, byte> OverflowRegistry => OverflowedPrimes;
internal static ConcurrentDictionary<UInt128, byte> OverflowRegistryWide => OverflowedPrimesWide;
internal static void OverrideCapabilitiesForTesting(PrimeOrderGpuCapability capability)
{
s_capability = capability;
}
internal static void ResetCapabilitiesForTesting()
{
s_capability = PrimeOrderGpuCapability.Default;
}
private static Kernel GetPartialFactorKernel(int acceleratorIndex)
{
var pool = _partialFactorKernel ??= [];
if (pool[acceleratorIndex] is {} cached)
{
return cached;
}
var accelerator = _accelerators[acceleratorIndex];
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, int, ulong, uint, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(PartialFactorKernel);
cached = KernelUtil.GetKernel(loaded);
pool[acceleratorIndex] = cached;
return cached;
}
public static bool TryPartialFactor(
PrimeOrderCalculatorAccelerator gpu,
ulong value,
uint limit,
Span<ulong> primeTargets,
Span<int> exponentTargets,
out int factorCount,
out ulong remaining)
{
factorCount = 0;
remaining = value;
if (primeTargets.Length == 0 || exponentTargets.Length == 0)
{
return false;
}
primeTargets.Clear();
exponentTargets.Clear();
if (!TryLaunchPartialFactorKernel(
gpu,
value,
limit,
primeTargets,
exponentTargets,
out int extracted,
out ulong leftover))
{
return false;
}
int capacity = Math.Min(primeTargets.Length, exponentTargets.Length);
if (extracted > capacity)
{
factorCount = 0;
remaining = value;
return false;
}
factorCount = extracted;
remaining = leftover;
return true;
}
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static bool TryLaunchPartialFactorKernel(
PrimeOrderCalculatorAccelerator gpu,
ulong value,
uint limit,
Span<ulong> primeTargets,
Span<int> exponentTargets,
out int factorCount,
out ulong remaining)
{
factorCount = 0;
remaining = value;
try
{
// GpuPrimeWorkLimiter.Acquire();
int acceleratorIndex = AcceleratorPool.Shared.Rent();
Accelerator accelerator = _accelerators[acceleratorIndex];
AcceleratorStream stream = accelerator.CreateStream();
var smallPrimesView = gpu.SmallPrimeFactorPrimes;
var smallSquaresView = gpu.SmallPrimeFactorSquares;
// TODO: We should create / reallocate these buffer only once or if the new required length is bigger than capacity.
MemoryBuffer1D<ulong, Stride1D.Dense>? factorBuffer = accelerator.Allocate1D<ulong>(primeTargets.Length);
MemoryBuffer1D<int, Stride1D.Dense>? exponentBuffer = accelerator.Allocate1D<int>(exponentTargets.Length);
MemoryBuffer1D<int, Stride1D.Dense>? countBuffer = accelerator.Allocate1D<int>(1);
MemoryBuffer1D<ulong, Stride1D.Dense>? remainingBuffer = accelerator.Allocate1D<ulong>(1);
MemoryBuffer1D<byte, Stride1D.Dense>? fullyFactoredBuffer = accelerator.Allocate1D<byte>(1);
// There is no need to clear these buffers because the kernel will always assign values within the required bounds. Keep it commented out.
// factorBuffer.MemSetToZero(stream);
// exponentBuffer.MemSetToZero(stream);
// countBuffer.MemSetToZero(stream);
// remainingBuffer.MemSetToZero(stream);
// fullyFactoredBuffer.MemSetToZero(stream);
var partialFactorKernel = GetPartialFactorKernel(acceleratorIndex);
var kernelLauncher = partialFactorKernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, int, ulong, uint, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>>();
kernelLauncher(
stream,
1,
smallPrimesView,
smallSquaresView,
(int)smallPrimesView.Length,
primeTargets.Length,
value,
limit,
factorBuffer.View,
exponentBuffer.View,
countBuffer.View,
remainingBuffer.View,
fullyFactoredBuffer.View);
countBuffer.View.CopyToCPU(stream, ref factorCount, 1);
factorBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(primeTargets), primeTargets.Length);
exponentBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(exponentTargets), exponentTargets.Length);
remainingBuffer.View.CopyToCPU(stream, ref remaining, 1);
byte fullyFactoredFlag = 0;
fullyFactoredBuffer.View.CopyToCPU(stream, ref fullyFactoredFlag, 1);
stream.Synchronize();
stream.Dispose();
factorCount = Math.Min(factorCount, primeTargets.Length);
// TODO: These buffers shouldn't be disposed but rather reused accross call with the assigned accelerator.
factorBuffer.Dispose();
exponentBuffer.Dispose();
countBuffer.Dispose();
remainingBuffer.Dispose();
fullyFactoredBuffer.Dispose();
// GpuPrimeWorkLimiter.Release();
return true;
}
catch (CLException ex)
{
Console.WriteLine($"GPU ERROR ({ex.Error}): {ex.Message}");
throw;
}
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
{
Console.WriteLine($"GPU ERROR: {ex.Message}");
throw;
}
}
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData divisorData)
{
Span<ulong> exponents = stackalloc ulong[1];
Span<ulong> remainders = stackalloc ulong[1];
exponents[0] = exponent;
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders, divisorData);
remainder = remainders[0];
return status;
}
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders, in MontgomeryDivisorData divisorData)
{
// This will never occur in production code
// if (exponents.Length == 0)
// {
// return GpuPow2ModStatus.Success;
// }
// This will never occur in production code
// if (remainders.Length < exponents.Length)
// {
// throw new ArgumentException("Remainder span is shorter than the exponent span.", nameof(remainders));
// }
// This will never occur in production code
// if (remainders.Length > exponents.Length)
// throw new ArgumentException("Remainder span is longer than the exponent span.", nameof(remainders));
// We need to clear more, but we save on allocations
// Span<ulong> target = remainders[..exponents.Length];
remainders.Clear();
// This will never occur in production code
// if (prime <= 1UL)
// {
// return GpuPow2ModStatus.Unavailable;
// }
ConcurrentDictionary<ulong, byte> overflowRegistry = OverflowedPrimes;
if (overflowRegistry.ContainsKey(prime))
{
return GpuPow2ModStatus.Overflow;
}
PrimeOrderGpuCapability capability = s_capability;
if (prime.GetBitLength() > capability.ModulusBits)
{
overflowRegistry[prime] = 0;
return GpuPow2ModStatus.Overflow;
}
for (int i = 0; i < exponents.Length; i++)
{
if (exponents[i].GetBitLength() > capability.ExponentBits)
{
return GpuPow2ModStatus.Overflow;
}
}
bool computed = TryComputeOnGpu(exponents, prime, divisorData, remainders);
return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable;
}
public static GpuPow2ModStatus TryPow2Mod(in UInt128 exponent, in UInt128 prime, out UInt128 remainder)
{
Span<UInt128> exponents = stackalloc UInt128[1];
Span<UInt128> remainders = stackalloc UInt128[1];
exponents[0] = exponent;
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders);
remainder = remainders[0];
return status;
}
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
{
return TryPow2ModBatchInternal(exponents, prime, remainders);
}
internal static bool TryCalculateOrder(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong? previousOrder,
PrimeOrderCalculator.PrimeOrderSearchConfig config,
in MontgomeryDivisorData divisorData,
out ulong order)
{
order = 0UL;
// GpuPrimeWorkLimiter.Acquire();
int acceleratorIndex = AcceleratorPool.Shared.Rent();
Accelerator accelerator = _accelerators[acceleratorIndex];
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
var smallPrimesView = gpu.SmallPrimeFactorPrimes;
var smallSquaresView = gpu.SmallPrimeFactorSquares;
// TODO: These buffers should be created reallocated once and assigned to an accelerator. Callers should use their own
// thread static cache to prevent other threads from using taking accelerators with pre-allocated buffers if they don't
// use them.
MemoryBuffer1D<ulong, Stride1D.Dense>? phiFactorBuffer;
MemoryBuffer1D<int, Stride1D.Dense>? phiExponentBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? workFactorBuffer;
MemoryBuffer1D<int, Stride1D.Dense>? workExponentBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? candidateBuffer;
MemoryBuffer1D<int, Stride1D.Dense>? stackIndexBuffer;
MemoryBuffer1D<int, Stride1D.Dense>? stackExponentBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? stackProductBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? resultBuffer;
MemoryBuffer1D<byte, Stride1D.Dense>? statusBuffer;
// lock (accelerator)
{
phiFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
phiExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
workFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
workExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
candidateBuffer = accelerator.Allocate1D<ulong>(HeuristicCandidateLimit);
stackIndexBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
stackExponentBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
stackProductBuffer = accelerator.Allocate1D<ulong>(HeuristicStackCapacity);
resultBuffer = accelerator.Allocate1D<ulong>(1);
statusBuffer = accelerator.Allocate1D<byte>(1);
}
// TODO: Remove the cleaning after the order kernel is modified to always set the result.
phiFactorBuffer.MemSetToZero(stream);
phiExponentBuffer.MemSetToZero(stream);
workFactorBuffer.MemSetToZero(stream);
workExponentBuffer.MemSetToZero(stream);
candidateBuffer.MemSetToZero(stream);
stackIndexBuffer.MemSetToZero(stream);
stackExponentBuffer.MemSetToZero(stream);
stackProductBuffer.MemSetToZero(stream);
resultBuffer.MemSetToZero(stream);
statusBuffer.MemSetToZero(stream);
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
byte hasPrevious = previousOrder.HasValue ? (byte)1 : (byte)0;
ulong previousValue = previousOrder ?? 0UL;
var kernelConfig = new OrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, (int)config.Mode);
var buffers = new OrderKernelBuffers(
phiFactorBuffer.View,
phiExponentBuffer.View,
workFactorBuffer.View,
workExponentBuffer.View,
candidateBuffer.View,
stackIndexBuffer.View,
stackExponentBuffer.View,
stackProductBuffer.View,
resultBuffer.View,
statusBuffer.View);
var orderKernel = GetOrderKernel(acceleratorIndex);
var kernelLauncher = orderKernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, OrderKernelConfig, ulong, ulong, ulong, ulong, ulong, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, OrderKernelBuffers>>();
kernelLauncher(
stream,
1,
prime,
kernelConfig,
divisorData.Modulus,
divisorData.NPrime,
divisorData.MontgomeryOne,
divisorData.MontgomeryTwo,
divisorData.MontgomeryTwoSquared,
smallPrimesView,
smallSquaresView,
(int)smallPrimesView.Length,
buffers);
byte status = 0;
statusBuffer.View.CopyToCPU(stream, ref status, 1);
resultBuffer.View.CopyToCPU(stream, ref order, 1);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
phiFactorBuffer.Dispose();
phiExponentBuffer.Dispose();
workFactorBuffer.Dispose();
workExponentBuffer.Dispose();
candidateBuffer.Dispose();
stackIndexBuffer.Dispose();
stackExponentBuffer.Dispose();
stackProductBuffer.Dispose();
resultBuffer.Dispose();
statusBuffer.Dispose();
// GpuPrimeWorkLimiter.Release();
PrimeOrderKernelStatus kernelStatus = (PrimeOrderKernelStatus)status;
if (kernelStatus == PrimeOrderKernelStatus.Fallback)
{
return false;
}
if (kernelStatus == PrimeOrderKernelStatus.PollardOverflow)
{
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
}
if (kernelStatus == PrimeOrderKernelStatus.FactoringFailure)
{
order = 0UL;
}
return order != 0UL;
}
private static Kernel GetOrderKernel(int acceleratorIndex)
{
var pool = _orderKernel ??= [];
if (pool[acceleratorIndex] is {} cached)
{
return cached;
}
var accelerator = _accelerators[acceleratorIndex];
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, OrderKernelConfig, ulong, ulong, ulong, ulong, ulong, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, OrderKernelBuffers>(CalculateOrderKernel);
var kernel = KernelUtil.GetKernel(loaded);
pool[acceleratorIndex] = kernel;
return kernel;
}
private static GpuPow2ModStatus TryPow2ModBatchInternal(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
{
if (exponents.Length == 0)
{
return GpuPow2ModStatus.Success;
}
if (remainders.Length < exponents.Length)
{
throw new ArgumentException("Remainder span is shorter than the exponent span.", nameof(remainders));
}
Span<UInt128> target = remainders[..exponents.Length];
target.Clear();
ConcurrentDictionary<UInt128, byte> overflowRegistryWide = OverflowedPrimesWide;
if (overflowRegistryWide.ContainsKey(prime))
{
return GpuPow2ModStatus.Overflow;
}
PrimeOrderGpuCapability capability = s_capability;
if (prime.GetBitLength() > capability.ModulusBits)
{
overflowRegistryWide[prime] = 0;
return GpuPow2ModStatus.Overflow;
}
for (int i = 0; i < exponents.Length; i++)
{
if (exponents[i].GetBitLength() > capability.ExponentBits)
{
return GpuPow2ModStatus.Overflow;
}
}
ComputeOnGpuWide(exponents, prime, target);
return GpuPow2ModStatus.Success;
}
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
{
// GpuPrimeWorkLimiter.Acquire();
int acceleratorIndex = AcceleratorPool.Shared.Rent();
Accelerator accelerator = _accelerators[acceleratorIndex];
AcceleratorStream? stream = accelerator.CreateStream();
// TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity.
// Modify the callers to use their own pool of buffers per accelerator, so that other threads don't use the accelerators
// with out preallocated buffers. Share the pool with Pow2ModWide kernel.
MemoryBuffer1D<ulong, Stride1D.Dense>? exponentBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? remainderBuffer;
// lock (accelerator)
{
exponentBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
remainderBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
}
exponentBuffer.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(exponents), exponents.Length);
// Pow2Mod kernel always assigns the required output elements so we don't need to worry about clearing these.
// remainderBuffer.MemSetToZero(stream);
var pow2ModKernel = GetPow2ModKernel(acceleratorIndex);
var kernelLauncher = pow2ModKernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, ulong, ulong, ulong, ulong, ulong, ArrayView1D<ulong, Stride1D.Dense>>>();
try
{
kernelLauncher(stream, exponents.Length, exponentBuffer.View, divisorData.Modulus, divisorData.NPrime, divisorData.MontgomeryOne, divisorData.MontgomeryTwo, divisorData.MontgomeryTwoSquared, remainderBuffer.View);
remainderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(results), exponents.Length);
stream.Synchronize();
stream.Dispose();
stream = null;
exponentBuffer.Dispose();
remainderBuffer.Dispose();
// GpuPrimeWorkLimiter.Release();
}
catch (Exception)
{
Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}.");
stream?.Dispose();
exponentBuffer.Dispose();
remainderBuffer.Dispose();
// GpuPrimeWorkLimiter.Release();
throw;
}
return true;
}
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorDataGpu divisorData, Span<ulong> results)
{
int length = exponents.Length;
for (int i = 0; i < length; i++)
{
results[i] = Pow2ModCpu(exponents[i], prime, divisorData);
}
}
private static void ComputeOnGpuWide(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> results)
{
int length = exponents.Length;
GpuUInt128[]? rentedExponents = null;
GpuUInt128[]? rentedResults = null;
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
PrimeOrderCalculatorAccelerator? gpu = null;
bool poolingRequired = length > WideStackThreshold;
Span<GpuUInt128> exponentSpan = poolingRequired
? stackalloc GpuUInt128[length]
: new Span<GpuUInt128>(rentedExponents = gpuUInt128Pool.Rent(length), 0, length);
Span<GpuUInt128> resultSpan = length <= WideStackThreshold
? stackalloc GpuUInt128[length]
: new Span<GpuUInt128>(rentedResults = gpuUInt128Pool.Rent(length), 0, length);
for (int i = 0; i < length; i++)
{
exponentSpan[i] = (GpuUInt128)exponents[i];
}
GpuUInt128 modulus = (GpuUInt128)prime;
gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
Accelerator accelerator = gpu.Accelerator;
AcceleratorStream? stream = AcceleratorStreamPool.Rent(acceleratorIndex);
MemoryBuffer1D<GpuUInt128, Stride1D.Dense> exponentBuffer;
MemoryBuffer1D<GpuUInt128, Stride1D.Dense> remainderBuffer;
// lock (accelerator)
{
exponentBuffer = accelerator.Allocate1D<GpuUInt128>(length);
remainderBuffer = accelerator.Allocate1D<GpuUInt128>(length);
}
exponentBuffer.View.CopyFromCPU(stream, exponentSpan);
var kernelLauncher = gpu.Pow2ModWideKernelLauncher;
kernelLauncher(stream, length, exponentBuffer.View, modulus, remainderBuffer.View);
remainderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(resultSpan), length);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
for (int i = 0; i < length; i++)
{
results[i] = (UInt128)resultSpan[i];
}
remainderBuffer.Dispose();
exponentBuffer.Dispose();
PrimeOrderCalculatorAccelerator.Return(gpu);
if (rentedExponents is not null)
{
gpuUInt128Pool.Return(rentedExponents, clearArray: false);
gpuUInt128Pool.Return(rentedResults!, clearArray: false);
}
}
private static void ComputePow2ModCpuWide(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> results)
{
int length = exponents.Length;
for (int i = 0; i < length; i++)
{
results[i] = exponents[i].Pow2MontgomeryModWindowed(prime);
}
}
private static ulong Pow2ModCpu(ulong exponent, ulong modulus, in MontgomeryDivisorDataGpu divisorData)
{
if (modulus <= 1UL)
{
return 0UL;
}
return ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisorData, exponent);
}
private static Kernel GetPow2ModKernel(int acceleratorIndex)
{
var pool = _pow2ModKernel ??= [];
if (pool[acceleratorIndex] is {} cached)
{
return cached;
}
var accelerator = _accelerators[acceleratorIndex];
var loaded = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ulong, ulong, ulong, ulong, ulong, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernel));
pool[acceleratorIndex] = loaded;
return loaded;
}
internal readonly record struct PrimeOrderGpuCapability(int ModulusBits, int ExponentBits)
{
public static PrimeOrderGpuCapability Default => new(128, 128);
}
}