Initial BitContradiction implementation on GPU

This commit is contained in:
mlemanczyk committed 2026-01-25 16:37:22 +01:00
1 parent e61d166eb1
commit 17763033e1
13 files changed
+1969 -23

No files matched your search

+24 -9
View File
@@ -112,18 +112,25 @@ private static ThreadLocal<object>? MersenneTesters;
}
Console.WriteLine("Initializing");
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads * PerfectNumberConstants.ThreadsByAccelerator);
int threadCount = Math.Max(1, _cliArguments.ThreadCount);
EnvironmentConfiguration.MinK = _cliArguments.MinK;
EnvironmentConfiguration.GpuBatchSize = Math.Max(1, _cliArguments.ScanBatchSize);
EnvironmentConfiguration.GpuRatio = _cliArguments.GpuRatio;
EnvironmentConfiguration.MersenneDevice = _cliArguments.MersenneDevice;
EnvironmentConfiguration.OrderDevice = _cliArguments.OrderDevice;
ComputationDevice orderDevice = _cliArguments.OrderDevice;
if (_cliArguments.MersenneDevice == ComputationDevice.Gpu &&
_cliArguments.ByDivisorKIncrementMode == ByDivisorKIncrementMode.BitContradiction)
{
orderDevice = ComputationDevice.Gpu;
}
EnvironmentConfiguration.OrderDevice = orderDevice;
EnvironmentConfiguration.UsePow2GroupDivisors = _cliArguments.ByDivisorKIncrementMode == ByDivisorKIncrementMode.Pow2Groups;
EnvironmentConfiguration.ByDivisorSpecialRange = _cliArguments.ByDivisorSpecialRange;
bool useGpu = _cliArguments.OrderDevice == ComputationDevice.Hybrid || _cliArguments.OrderDevice == ComputationDevice.Gpu ||
bool useGpu = orderDevice == ComputationDevice.Hybrid || orderDevice == ComputationDevice.Gpu ||
_cliArguments.MersenneDevice == ComputationDevice.Hybrid || _cliArguments.MersenneDevice == ComputationDevice.Gpu;
EnvironmentConfiguration.RollingAccelerators = useGpu
@@ -132,6 +139,10 @@ private static ThreadLocal<object>? MersenneTesters;
EnvironmentConfiguration.Initialize();
PrimeOrderCalculatorAccelerator.SkipOrderTablesAndKernels = _cliArguments.MersenneDevice == ComputationDevice.Gpu &&
_cliArguments.UseByDivisor &&
_cliArguments.ByDivisorKIncrementMode == ByDivisorKIncrementMode.BitContradiction;
// NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend;
// NttGpuMath.ReductionMode = _cliArguments.ModReductionMode;
_runPrimesOnCpu = _cliArguments.ForcePrimeKernelsOnCpu;
@@ -148,7 +159,16 @@ private static ThreadLocal<object>? MersenneTesters;
if (useGpu)
{
Console.WriteLine("Warming up GPU kernels");
PrimeTester.WarmUpGpuKernels(EnvironmentConfiguration.RollingAccelerators);
if (_cliArguments.UseByDivisor &&
_cliArguments.ByDivisorKIncrementMode == ByDivisorKIncrementMode.BitContradiction &&
_cliArguments.MersenneDevice == ComputationDevice.Gpu)
{
PrimeTester.WarmUpGpuKernelsForBitContradiction(EnvironmentConfiguration.RollingAccelerators);
}
else
{
PrimeTester.WarmUpGpuKernels(EnvironmentConfiguration.RollingAccelerators);
}
}
// PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4);
@@ -328,11 +348,6 @@ private static ThreadLocal<object>? MersenneTesters;
}
else if (useByDivisor)
{
if (mersenneOnGpu)
{
throw new NotSupportedException("--mersenne-device=gpu is not supported for --mersenne=bydivisor on this execution path.");
}
ByDivisorKIncrementMode kIncrementMode = _cliArguments.ByDivisorKIncrementMode;
if (EnvironmentConfiguration.UseCpuOrder)
{
@@ -5,6 +5,7 @@ using Xunit;
namespace PerfectNumbers.Core.Tests;
[Trait("Category", "Fast")]
public class BitContradictionIntegrationTests
{
// 2^11 - 1 = 2047 = 23 * 89
@@ -39,11 +40,21 @@ public class BitContradictionIntegrationTests
divides.Should().BeFalse();
}
[Fact]
public void BitContradictionAcceptsKnownLargeDivisor()
{
BigInteger divisor = BigInteger.Parse("1209708008767");
ulong p = 138000001;
bool decided = Invoke(divisor, p, out bool divides);
decided.Should().BeTrue();
divides.Should().BeTrue();
}
[Fact]
public void BitContradictionRejectsNonDivisorWithDecision()
{
// 2^11 - 1 is not divisible by 25
BigInteger divisor = 25;
// 2^11 - 1 is not divisible by 45 (k=2, q=22*2+1).
BigInteger divisor = 45;
ulong p = 11;
bool decided = Invoke(divisor, p, out bool divides);
decided.Should().BeTrue("small p should be decided exactly");
@@ -0,0 +1,80 @@
using System;
using System.Numerics;
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Kernels;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class BitContradictionKernelsTests
{
[Theory]
[Trait("Category", "Fast")]
[InlineData(31UL, 0UL, 0)]
[InlineData(31UL, 0UL, 5)]
[InlineData(61UL, 7UL, 3)]
[InlineData(36721UL, 1UL, 0)]
public void TryBuildQWordsFromBatchIndex_matches_BigInteger(ulong exponent, ulong batchIndex, int batchOffset)
{
int wordCount = BitContradictionKernels.MaxQWordCount;
ulong[] batchIndexWords = new ulong[wordCount];
batchIndexWords[0] = batchIndex;
ulong[] qWords = new ulong[wordCount];
bool ok = BitContradictionKernels.TryBuildQWordsFromBatchIndex(
exponent,
batchIndexWords,
wordCount,
batchOffset,
qWords,
out int qBitLen);
ok.Should().BeTrue();
BigInteger expectedK = ((BigInteger)batchIndex * BitContradictionKernels.BatchCount) + BigInteger.One + batchOffset;
BigInteger expectedQ = (expectedK * (exponent << 1)) + BigInteger.One;
BigInteger actualQ = ReadBigIntegerFromWords(qWords);
actualQ.Should().Be(expectedQ);
qBitLen.Should().Be(GetBitLength(expectedQ));
}
private static BigInteger ReadBigIntegerFromWords(ReadOnlySpan<ulong> words)
{
byte[] bytes = new byte[words.Length * sizeof(ulong)];
for (int w = 0; w < words.Length; w++)
{
ulong word = words[w];
int byteIndex = w << 3;
bytes[byteIndex] = (byte)word;
bytes[byteIndex + 1] = (byte)(word >> 8);
bytes[byteIndex + 2] = (byte)(word >> 16);
bytes[byteIndex + 3] = (byte)(word >> 24);
bytes[byteIndex + 4] = (byte)(word >> 32);
bytes[byteIndex + 5] = (byte)(word >> 40);
bytes[byteIndex + 6] = (byte)(word >> 48);
bytes[byteIndex + 7] = (byte)(word >> 56);
}
return new BigInteger(bytes, isUnsigned: true, isBigEndian: false);
}
private static int GetBitLength(BigInteger value)
{
if (value.IsZero)
{
return 0;
}
byte[] bytes = value.ToByteArray(isUnsigned: true, isBigEndian: false);
int last = bytes[^1];
int bits = (bytes.Length - 1) * 8;
while (last != 0)
{
bits++;
last >>= 1;
}
return bits;
}
}
@@ -16,7 +16,7 @@ public class BitContradictionSolverTests
ColumnBounds bounds = ComputeColumnBounds(a, q, 1);
bounds.ForcedOnes.Should().Be(1);
bounds.PossibleOnes.Should().Be(1);
bounds.PossibleOnes.Should().Be(2);
}
[Fact]
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using System.Numerics;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.ByDivisor;
@@ -119,6 +120,7 @@ internal sealed class PrivateWorkSet
/// interval-based carry propagation described in the BitContradictions plan document.
/// The solver itself will build on top of these primitives.
/// </summary>
[DeviceDependentTemplate(typeof(ComputationDevice))]
internal static class BitContradictionSolverWithAMultiplier
{
private const int PingPongBlockColumns = 4096;
@@ -1510,6 +1512,11 @@ internal static class BitContradictionSolverWithAMultiplier
out bool divides)
#endif
{
if (!_initialized)
{
Initialize();
}
PrivateWorkSet workSet = _privateWorkSet;
divides = false;
@@ -2070,6 +2077,22 @@ internal static class BitContradictionSolverWithAMultiplier
// }
divides = true;
if (p <= 63 && divides)
{
ulong qLow = BuildQLow64(qOneOffsets);
if ((qLow & 1UL) == 0UL)
{
divides = false;
}
else
{
BigInteger qExact = new BigInteger(qLow);
if (BigInteger.ModPow(2, (int)p, qExact) != BigInteger.One)
{
divides = false;
}
}
}
#if DETAILED_LOG
reason = ContradictionReason.None;
PrintStats();
@@ -11,7 +11,9 @@ using System.Globalization;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Buffers;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Kernels;
using PerfectNumbers.Core.Gpu.Accelerators;
using PerfectNumbers.Core.ByDivisor;
using PerfectNumbers.Core.Persistence;
@@ -58,6 +60,226 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
private ulong _currentPrime;
#if DEVICE_GPU
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsBitContradictionGpu(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger minK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
processedAll = false;
foundDivisor = BigInteger.Zero;
BigInteger currentK = minK;
bool hasLimit = !allowedMax.IsZero;
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
BigInteger maxQ = (BigInteger.One << BitContradictionKernels.MaxQBitLength) - BigInteger.One;
BigInteger maxKByBits = (maxQ - BigInteger.One) / step;
if (!hasLimit || maxKByBits < maxK)
{
maxK = maxKByBits;
}
if (maxK < currentK)
{
processedAll = hasLimit;
return false;
}
PrimeOrderCalculatorAccelerator gpu = Accelerator;
int maxThreadCount = PerfectNumberConstants.BitContradictionGpuThreads;
int batchSize = PerfectNumberConstants.BitContradictionGpuBatchCount;
BigInteger batchSizeBig = batchSize;
int wordCount = BitContradictionKernels.MaxQWordCount;
int inputCapacity = maxThreadCount * wordCount;
gpu.EnsureCapacity(PerfectNumberConstants.DefaultFactorsBuffer, inputCapacity);
gpu.EnsurePartialFactorCapacity(inputCapacity);
var stream = AcceleratorStreamPool.Rent(gpu.AcceleratorIndex);
var kernels = GpuKernelPool.GetOrAddKernels(gpu.AcceleratorIndex, stream, KernelType.BitContradictionScan);
var kernel = kernels.BitContradiction!;
int[] found = new int[maxThreadCount];
ulong[] batchIndexWords = new ulong[inputCapacity];
BigInteger currentBatch = (currentK - BigInteger.One) / batchSizeBig;
currentK = (currentBatch * batchSizeBig) + BigInteger.One;
BigInteger maxBatch = (maxK - BigInteger.One) / batchSizeBig;
while (true)
{
if (hasLimit && currentBatch > maxBatch)
{
processedAll = true;
break;
}
BigInteger remainingBatches = maxBatch - currentBatch + BigInteger.One;
if (remainingBatches <= BigInteger.Zero)
{
processedAll = hasLimit;
break;
}
int threadCount = maxThreadCount;
if (remainingBatches < threadCount)
{
threadCount = (int)remainingBatches;
}
if (currentBatch > ulong.MaxValue)
{
throw new InvalidOperationException($"BitContradiction GPU batch index overflow for p={prime}, batch={currentBatch}");
}
BigInteger maxBatchIndex = currentBatch + (threadCount - 1);
if (maxBatchIndex > ulong.MaxValue)
{
throw new InvalidOperationException($"BitContradiction GPU batch index overflow for p={prime}, batch={currentBatch}, threads={threadCount}");
}
int countQ = batchSize;
for (int t = 0; t < threadCount; t++)
{
BigInteger batchIndex = currentBatch + t;
WriteBigIntegerToWords(batchIndex, batchIndexWords.AsSpan(t * wordCount, wordCount));
}
gpu.InputView.CopyFromCPU(stream, batchIndexWords.AsSpan(0, threadCount * wordCount));
kernel(stream, threadCount, prime, gpu.InputView, countQ, gpu.OutputIntView);
stream.Synchronize();
gpu.OutputIntView.CopyToCPU(stream, found.AsSpan(0, threadCount));
for (int i = 0; i < threadCount; i++)
{
int foundIndex = found[i];
if (foundIndex >= 0)
{
BigInteger batchIndex = currentBatch + i;
BigInteger candidateK = (batchIndex * batchSizeBig) + BigInteger.One + foundIndex;
if (hasLimit && candidateK > maxK)
{
processedAll = true;
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return false;
}
BigInteger candidateQ = (step * candidateK) + BigInteger.One;
Span<ulong> debugQWords = stackalloc ulong[BitContradictionKernels.MaxQWordCount];
if (!BitContradictionKernels.TryBuildQWordsFromBatchIndex(prime, batchIndexWords.AsSpan(i * wordCount, wordCount), wordCount, foundIndex, debugQWords, out int debugQBitLen))
{
throw new InvalidOperationException($"BitContradiction GPU qWords overflow for p={prime}, k={candidateK}, batch={batchIndex}, index={foundIndex}");
}
BigInteger debugQ = ReadBigIntegerFromWords(debugQWords);
if (debugQ != candidateQ)
{
throw new InvalidOperationException($"BitContradiction GPU qWords mismatch for p={prime}, k={candidateK}, batch={batchIndex}, index={foundIndex}, cpuQ={candidateQ}, gpuQ={debugQ}, gpuQBits={debugQBitLen}");
}
#if DivisorSet_BitContradiction
int[]? qBits = _qBits;
if (qBits == null || qBits.Length < (int)prime)
{
qBits = new int[(int)prime];
_qBits = qBits;
}
bool cpuDecided = TryExactBitContradictionCheck(prime, candidateQ, candidateK, out bool cpuDivides);
if (!cpuDecided)
{
throw new InvalidOperationException($"BitContradiction CPU undecided for p={prime}, q={candidateQ}, k={candidateK}, batch={batchIndex}");
}
if (!cpuDivides)
{
throw new InvalidOperationException($"BitContradiction CPU/GPU mismatch for p={prime}, q={candidateQ}, k={candidateK}, batch={batchIndex}");
}
#endif
bool dividesExact = BigInteger.ModPow(2, prime, candidateQ) == BigInteger.One;
if (!dividesExact)
{
throw new InvalidOperationException($"BitContradiction CPU/GPU false positive for p={prime}, q={candidateQ}, k={candidateK}, batch={batchIndex}");
}
foundDivisor = candidateQ;
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return true;
}
}
ContinueOuter:
RecordState(currentK);
if (hasLimit && currentK > maxK)
{
processedAll = true;
break;
}
currentBatch += threadCount;
currentK = (currentBatch * batchSizeBig) + BigInteger.One;
}
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return false;
}
private static void WriteBigIntegerToWords(BigInteger value, Span<ulong> words)
{
Span<byte> bytes = stackalloc byte[BitContradictionKernels.MaxQWordCount * 8];
if (!value.TryWriteBytes(bytes, out int written, isUnsigned: true, isBigEndian: false))
{
written = 0;
}
int wordCount = words.Length;
for (int w = 0; w < wordCount; w++)
{
ulong word = 0UL;
int byteIndex = w << 3;
int limit = written - byteIndex;
if (limit > 0)
{
if (limit > 8)
{
limit = 8;
}
for (int b = 0; b < limit; b++)
{
word |= (ulong)bytes[byteIndex + b] << (b << 3);
}
}
words[w] = word;
}
}
private static BigInteger ReadBigIntegerFromWords(ReadOnlySpan<ulong> words)
{
Span<byte> bytes = stackalloc byte[BitContradictionKernels.MaxQWordCount * 8];
int wordCount = words.Length;
for (int w = 0; w < wordCount; w++)
{
ulong word = words[w];
int byteIndex = w << 3;
bytes[byteIndex] = (byte)word;
bytes[byteIndex + 1] = (byte)(word >> 8);
bytes[byteIndex + 2] = (byte)(word >> 16);
bytes[byteIndex + 3] = (byte)(word >> 24);
bytes[byteIndex + 4] = (byte)(word >> 32);
bytes[byteIndex + 5] = (byte)(word >> 40);
bytes[byteIndex + 6] = (byte)(word >> 48);
bytes[byteIndex + 7] = (byte)(word >> 56);
}
return new BigInteger(bytes, isUnsigned: true, isBigEndian: false);
}
#endif
[ThreadStatic]
private static Dictionary<ulong, ulong>? _pow2DivisorPowModCache;
#if DETAILED_LOG
@@ -578,7 +800,6 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out processedAll,
out foundDivisor);
}
#endif
// return CheckDivisorsLarge(
// prime,
@@ -596,6 +817,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
step,
out processedAll,
out foundDivisor);
#endif
}
#if DivisorSet_Pow2Groups
@@ -1421,6 +1643,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return true;
}
ContinueOuter:
RecordState(currentK);
MOVE_NEXT:
@@ -1732,6 +1955,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
{
processedAll = false;
foundDivisor = BigInteger.Zero;
bool coversRange = true;
if (minK > ulong.MaxValue)
{
@@ -2130,7 +2354,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
{
if (((primeDecimalMask >> remainder10) & 1) != 0)
{
RecordState(currentK);
ContinueOuter:
RecordState(currentK);
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
// if (powResult.IsOne && !IsMersenneValue(prime, divisor))
if (powResult.IsOne)
@@ -2757,7 +2982,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
#endif
}
RecordState(currentK);
ContinueOuter:
RecordState(currentK);
// if (divisorCycle == prime && !IsMersenneValue(prime, candidate))
if (divisorCycle == prime)
{
@@ -2877,7 +3103,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
if (residueStepper.IsAdmissible())
{
// Stopwatch sw = Stopwatch.StartNew();
RecordState(currentK);
ContinueOuter:
RecordState(currentK);
ulong powMod;
bool isPow2 = IsPowerOfTwo(currentK);
#if DETAILED_LOG
@@ -2978,7 +3205,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
byte remainder8 = rem8.ComputeNext(divisor);
if (remainder8 == 1 || remainder8 == 7)
{
RecordState(currentK);
ContinueOuter:
RecordState(currentK);
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
if (powResult.IsOne)
{
@@ -3677,7 +3905,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
byte remainder8 = rem8.ComputeNext(divisor);
if (remainder8 == 1 || remainder8 == 7)
{
RecordState(currentK);
ContinueOuter:
RecordState(currentK);
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
// if (powResult.IsOne && !IsMersenneValue(prime, divisor))
if (powResult.IsOne)
@@ -4031,7 +4260,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
divisorCycle = computedCycle;
}
RecordState(currentK);
ContinueOuter:
RecordState(currentK);
if (divisorCycle == prime && !IsMersenneValue(prime, divisor))
{
foundDivisor = divisor;
@@ -4585,6 +4815,10 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out bool processedAll,
out BigInteger foundDivisor)
{
#if DEVICE_GPU
return CheckDivisorsBitContradictionGpu(prime, primeDecimalMask, allowedMax, minK, step, out processedAll, out foundDivisor);
#else
processedAll = true;
foundDivisor = BigInteger.Zero;
@@ -4643,6 +4877,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
}
MOVE_NEXT:
ContinueOuter:
RecordState(currentK);
if (hasLimit && currentK >= maxK)
{
@@ -4707,6 +4942,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
// step,
// out processedAll,
// out foundDivisor);
#endif
}
[ThreadStatic]
@@ -56,7 +56,11 @@ public sealed class PrimeOrderCalculatorAccelerator
internal static void WarmUp()
{
Accelerator[] accelerators = AcceleratorPool.Shared.Accelerators;
int acceleratorCount = accelerators.Length;
int acceleratorCount = EnvironmentConfiguration.RollingAccelerators;
if (acceleratorCount > accelerators.Length)
{
acceleratorCount = accelerators.Length;
}
for (var i = 0; i < acceleratorCount; i++)
{
Console.WriteLine($"Preparing accelerator {i}...");
@@ -64,6 +68,13 @@ public sealed class PrimeOrderCalculatorAccelerator
AcceleratorStreamPool.WarmUp(i);
// Don't take this from the pool as quick uploads of data to the accelerator consumes much of GPU's memory and throws.
AcceleratorStream stream = accelerator.CreateStream();
if (SkipOrderTablesAndKernels)
{
stream.Synchronize();
stream.Dispose();
continue;
}
// TODO: Review which tables are needed when final execution path is defined.
LastDigitGpuTables.WarmUp(i, stream);
_kernels[i] = new CalculatorKernels(accelerator);
@@ -99,6 +110,9 @@ public sealed class PrimeOrderCalculatorAccelerator
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public static bool SkipOrderTablesAndKernels;
public readonly Accelerator Accelerator;
public readonly int AcceleratorIndex;
@@ -411,6 +425,28 @@ public sealed class PrimeOrderCalculatorAccelerator
CalculateOrderWideExponentBufferView = CalculateOrderWideExponentBuffer.View;
CalculateOrderWideRemainderBufferView = CalculateOrderWideRemainderBuffer.View;
if (SkipOrderTablesAndKernels)
{
DevicePrimesLastOne = default;
DevicePrimesLastSeven = default;
DevicePrimesLastThree = default;
DevicePrimesLastNine = default;
DevicePrimesPow2LastOne = default;
DevicePrimesPow2LastSeven = default;
DevicePrimesPow2LastThree = default;
DevicePrimesPow2LastNine = default;
SmallPrimeFactorPrimes = default;
SmallPrimeFactorSquares = default;
CheckFactorsKernel = default;
ConvertToStandardKernelLauncher = default;
HeuristicCombinedTrialDivisionKernelLauncher = default;
Pow2ModKernelLauncher = default;
Pow2ModWideKernelLauncher = default;
SmallPrimeFactorKernelLauncher = default;
SpecialMaxKernelLauncher = default;
return;
}
LastDigitGpuTables lastDigitSharedTables = LastDigitGpuTables.GetStaticTables(acceleratorIndex);
DevicePrimesLastOne = lastDigitSharedTables.DevicePrimesLastOne.View;
DevicePrimesLastSeven = lastDigitSharedTables.DevicePrimesLastSeven.View;
+19 -1
View File
@@ -2,6 +2,7 @@ using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
using PerfectNumbers.Core.Gpu.Kernels;
namespace PerfectNumbers.Core.Gpu;
@@ -26,7 +27,7 @@ public class GpuKernelPool
internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType)
{
var pool = _pool ??= [];
var pool = _pool ??= new KernelContainer[_accelerators.Length];
if (pool[acceleratorIndex] is not { } kernels)
{
kernels = new();
@@ -58,6 +59,11 @@ public class GpuKernelPool
InitPow2ModOrderKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.BitContradictionScan))
{
InitBitContradictionKernel(accelerator, kernels);
}
PreloadStaticTables(accelerator, kernels, stream, kernelType);
pool[acceleratorIndex] = kernels;
@@ -113,6 +119,18 @@ public class GpuKernelPool
});
}
static void InitBitContradictionKernel(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.BitContradiction, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<int, Stride1D.Dense>>(BitContradictionKernels.BitContradictionKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<int, Stride1D.Dense>>>();
});
}
static void InitPow2ModOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.Pow2ModOrder, () =>
@@ -22,6 +22,9 @@ public sealed class KernelContainer
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int,
ArrayView1D<int, Stride1D.Dense>>? BitContradiction;
public Kernel? SmallPrimeFactor;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SpecialMax;
+2 -1
View File
@@ -10,5 +10,6 @@ public enum KernelType
OrderKernelScan,
IncrementalKernelScan,
IncrementalOrderKernelScan,
Pow2ModOrderKernelScan
Pow2ModOrderKernelScan,
BitContradictionScan
}
File diff suppressed because it is too large. Load diff
@@ -20,12 +20,14 @@ public static class PerfectNumberConstants
public const int PooledArrayThreshold = 64;
public const int Pow2WindowSize = 8;
public const int RollingAccelerators = 298;
public const int ThreadsByAccelerator = 1;
public const int ThreadsByAccelerator = 128;
public const int ByDivisorStateSaveInterval = 1_000;
public const string ByDivisorStateDirectory = "Checks";
public const int PollardRhoMilliseconds = 100;
public const int ByDivisorMersenneTailCount = 8;
public const int ByDivisorMersenneBitLimit = 4096;
public const int BitContradictionGpuBatchCount = 16_777_216;
public const int BitContradictionGpuThreads = 4096;
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
// divisor-cycle datasets we generate offline.
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
+41
View File
@@ -1,6 +1,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core;
@@ -157,6 +158,45 @@ public sealed class PrimeTester
private static readonly object GpuWarmUpLock = new();
private static int WarmedGpuLeaseCount;
private static int WarmedBitContradictionGpuLeaseCount;
public static void WarmUpGpuKernelsForBitContradiction(int threadCount)
{
int target = threadCount;
if (target == 0)
{
target = threadCount;
}
lock (GpuWarmUpLock)
{
PrimeOrderCalculatorAccelerator.SkipOrderTablesAndKernels = true;
if (target <= WarmedBitContradictionGpuLeaseCount)
{
return;
}
Accelerator[] accelerators = AcceleratorPool.Shared.Accelerators;
int acceleratorCount = EnvironmentConfiguration.RollingAccelerators;
if (acceleratorCount > accelerators.Length)
{
acceleratorCount = accelerators.Length;
}
for (var i = 0; i < acceleratorCount; i++)
{
Console.WriteLine($"Preparing accelerator {i}...");
AcceleratorStreamPool.WarmUp(i);
Accelerator accelerator = accelerators[i];
AcceleratorStream stream = accelerator.CreateStream();
_ = GpuKernelPool.GetOrAddKernels(i, stream, KernelType.BitContradictionScan);
stream.Synchronize();
stream.Dispose();
}
WarmedBitContradictionGpuLeaseCount = target;
}
}
public static void WarmUpGpuKernels(int threadCount)
{
@@ -173,6 +213,7 @@ public sealed class PrimeTester
return;
}
PrimeOrderCalculatorAccelerator.SkipOrderTablesAndKernels = false;
PrimeOrderCalculatorAccelerator.WarmUp();
WarmedGpuLeaseCount = target;
}