GpuContextPool removed, because we share context and accelerator between threads, using streams to separate.

This commit is contained in:
mlemanczyk committed 2025-11-07 12:52:20 +01:00
1 parent 7c035239b4
commit f4e365194d
19 files changed
+285 -425

No files matched your search

+1 -1
View File
@@ -269,7 +269,7 @@ public sealed class DivisorCycleCache
int length = divisors.Length; int length = divisors.Length;
var gpuLease = GpuKernelPool.Rent(); var gpuLease = GpuKernelPool.Rent();
Accelerator accelerator = gpuLease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
var kernel = _gpuKernel; var kernel = _gpuKernel;
EnsureCapacity(length); EnsureCapacity(length);
-107
View File
@@ -1,107 +0,0 @@
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
public sealed class GpuContextLease
{
private GpuContextPool.PooledContext? _ctx;
internal GpuContextLease(GpuContextPool.PooledContext ctx)
{
_ctx = ctx;
}
public Context Context => _ctx!.Context;
public Accelerator Accelerator => _ctx!.Accelerator;
public void Dispose() => GpuContextPool.Return(_ctx!);
}
public static class GpuContextPool
{
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
internal sealed class PooledContext
{
public readonly Context Context;
public readonly Accelerator Accelerator;
public PooledContext()
{
Context = SharedGpuContext.Context;
Accelerator = SharedGpuContext.Accelerator;
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
// CL stream/queue creation during accelerator construction.
// Some OpenCL drivers are fragile when a queue is created immediately
// after device init. Real kernels will be JIT-loaded at first use.
}
public void Dispose()
{
// Ensure all cached GPU buffers for this accelerator are released.
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
// Clear any per-accelerator cached resources to avoid releasing
// after the accelerator is destroyed.
// These resources are shared between GPU leases
// Accelerator.Dispose();
// Context.Dispose();
}
}
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
private static int WarmedGpuContextCount;
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease Rent()
{
if (GpuPool.TryDequeue(out var gpu))
{
return new GpuContextLease(gpu);
}
// Create a new accelerator when the pool does not have one available.
return new GpuContextLease(new PooledContext());
}
public static void WarmUpPool(int threadCount)
{
if (threadCount <= WarmedGpuContextCount)
{
return;
}
int toCreate = threadCount - WarmedGpuContextCount;
Console.WriteLine($"Warming up GPU context pool to {toCreate} contexts...");
var contexts = new PooledContext[toCreate];
var pool = GpuPool;
PooledContext context;
for (int i = 0; i < toCreate; i++)
{
context = new PooledContext();
contexts[i] = context;
pool.Enqueue(context);
}
WarmedGpuContextCount = threadCount;
}
public static void DisposeAll()
{
while (GpuPool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
WarmedGpuContextCount = 0;
}
internal static void Return(PooledContext ctx)
{
GpuPool.Enqueue(ctx);
}
}
+100 -114
View File
@@ -1,17 +1,15 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public sealed class GpuKernelLease public sealed class GpuKernelLease
{ {
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new(); private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
private GpuContextLease _gpu; public KernelContainer Kernels;
public KernelContainer Kernels; public AcceleratorStream Stream;
public AcceleratorStream Stream;
#pragma warning disable CS8618 // _gpu and Stream will be set by Rent method #pragma warning disable CS8618 // _gpu and Stream will be set by Rent method
private GpuKernelLease(KernelContainer kernels) private GpuKernelLease(KernelContainer kernels)
@@ -20,125 +18,113 @@ public sealed class GpuKernelLease
Kernels = kernels; Kernels = kernels;
} }
internal static GpuKernelLease Rent(GpuContextLease gpu, AcceleratorStream stream, KernelContainer kernels) internal static GpuKernelLease Rent(AcceleratorStream stream, KernelContainer kernels)
{ {
if (!Pool.TryDequeue(out var lease)) if (!Pool.TryDequeue(out var lease))
{ {
lease = new GpuKernelLease(kernels); lease = new GpuKernelLease(kernels);
} }
lease._gpu = gpu;
lease.Kernels = kernels; lease.Kernels = kernels;
lease.Stream = stream; lease.Stream = stream;
return lease; return lease;
} }
public Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released"); public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
{
get
{
return GpuKernelPool.InitOnce(ref Kernels!.Order, () =>
{
var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
{ {
get get
{ {
var accel = Accelerator; // avoid capturing 'this' in lambda return GpuKernelPool.InitOnce(ref Kernels!.Pow2Mod, () =>
return GpuKernelPool.InitOnce(ref Kernels!.Order, () => {
{ var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan);
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan); var kernel = KernelUtil.GetKernel(loaded);
var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>(); });
}); }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
{ {
get get
{ {
var accel = Accelerator; return GpuKernelPool.InitOnce(ref Kernels!.Incremental, () =>
return GpuKernelPool.InitOnce(ref Kernels!.Pow2Mod, () => {
{ var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan);
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan); var kernel = KernelUtil.GetKernel(loaded);
var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>(); });
}); }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
{ {
get get
{ {
var accel = Accelerator; // avoid capturing 'this' in lambda return GpuKernelPool.InitOnce(ref Kernels!.IncrementalOrder, () =>
return GpuKernelPool.InitOnce(ref Kernels!.Incremental, () => {
{ var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan); var kernel = KernelUtil.GetKernel(loaded);
var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>(); });
}); }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
{ {
get get
{ {
var accel = Accelerator; return GpuKernelPool.InitOnce(ref Kernels!.Pow2ModOrder, () =>
return GpuKernelPool.InitOnce(ref Kernels!.IncrementalOrder, () => {
{ var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded);
var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>(); });
}); }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SpecialMaxKernel
{ {
get get
{ {
var accel = Accelerator; return GpuKernelPool.InitOnce(ref Kernels!.SpecialMax, () =>
return GpuKernelPool.InitOnce(ref Kernels!.Pow2ModOrder, () => {
{ var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel);
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded);
var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>(); });
}); }
} }
}
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SpecialMaxKernel public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel
{ {
get get
{ {
var accel = Accelerator; return GpuKernelPool.InitOnce(ref Kernels!.SmallPrimeFactor, () =>
return GpuKernelPool.InitOnce(ref Kernels!.SpecialMax, () => {
{ var loaded = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan);
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel); var kernel = KernelUtil.GetKernel(loaded);
var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>(); });
}); }
} }
}
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel public void Dispose()
{ {
get Stream.Dispose();
{ Pool.Enqueue(this);
var accel = Accelerator;
return GpuKernelPool.InitOnce(ref Kernels!.SmallPrimeFactor, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
public void Dispose()
{
Stream.Dispose();
_gpu.Dispose();
Pool.Enqueue(this);
GpuPrimeWorkLimiter.Release(); GpuPrimeWorkLimiter.Release();
} }
} }
+47 -26
View File
@@ -1,13 +1,12 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public class GpuKernelPool public class GpuKernelPool
{ {
private static readonly ConcurrentDictionary<Accelerator, KernelContainer> _kernels = new(); public static readonly KernelContainer Kernels = CreateKernels();
public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class
{ {
@@ -20,26 +19,35 @@ public class GpuKernelPool
return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue; return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue;
} }
internal static KernelContainer GetKernels(Accelerator accelerator, AcceleratorStream stream) private static KernelContainer CreateKernels()
{ {
return _kernels.GetOrAdd(accelerator, (accelerator) => var kernels = new KernelContainer();
{ var stream = SharedGpuContext.Accelerator.CreateStream();
var kernels = new KernelContainer(); PreloadStaticTables(kernels, stream);
PreloadStaticTables(kernels, accelerator, stream); stream.Synchronize();
return kernels; stream.Dispose();
}); return kernels;
} }
internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator, AcceleratorStream stream) private static KernelContainer CreateKernels(AcceleratorStream stream)
{ {
EnsureSmallCyclesOnDevice(kernels, accelerator, stream); KernelContainer kernels = new();
EnsureSmallPrimesOnDevice(kernels, accelerator, stream); PreloadStaticTables(kernels, stream);
EnsureSmallPrimeFactorTables(kernels, accelerator, stream); return Kernels;
} }
internal static void PreloadStaticTables(KernelContainer kernels, AcceleratorStream stream)
{
_ = EnsureSmallCyclesOnDevice(kernels, stream);
_ = EnsureSmallPrimesOnDevice(kernels, stream);
_ = EnsureSmallPrimeFactorTables(kernels, stream);
}
public static ArrayView1D<ulong, Stride1D.Dense> GetSmallCyclesOnDevice() => Kernels.SmallCycles!;
// Ensures the small cycles table is uploaded to the device for the given accelerator. // Ensures the small cycles table is uploaded to the device for the given accelerator.
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it). // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(KernelContainer kernels, Accelerator accelerator, AcceleratorStream stream) private static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(KernelContainer kernels, AcceleratorStream stream)
{ {
if (kernels.SmallCycles is { } buffer) if (kernels.SmallCycles is { } buffer)
{ {
@@ -47,13 +55,20 @@ public class GpuKernelPool
} }
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot. var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot.
var device = accelerator.Allocate1D<ulong>(host.Length); var device = SharedGpuContext.Accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(stream, host); device.View.CopyFromCPU(stream, host);
kernels.SmallCycles = device; kernels.SmallCycles = device;
return device.View; return device.View;
} }
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(KernelContainer kernels, Accelerator accelerator, AcceleratorStream stream) public static ResiduePrimeViews GetSmallPrimesOnDevice() => new(
Kernels.SmallPrimesLastOne!,
Kernels.SmallPrimesLastSeven!,
Kernels.SmallPrimesPow2LastOne!,
Kernels.SmallPrimesPow2LastSeven!
);
private static ResiduePrimeViews EnsureSmallPrimesOnDevice(KernelContainer kernels, AcceleratorStream stream)
{ {
if (kernels.SmallPrimesLastOne is { } lastOne && if (kernels.SmallPrimesLastOne is { } lastOne &&
kernels.SmallPrimesLastSeven is { } lastSeven && kernels.SmallPrimesLastSeven is { } lastSeven &&
@@ -71,6 +86,7 @@ public class GpuKernelPool
return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View); return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View);
} }
Accelerator accelerator = SharedGpuContext.Accelerator;
var hostLastOne = PrimesGenerator.SmallPrimesLastOne; var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven;
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
@@ -93,34 +109,39 @@ public class GpuKernelPool
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
} }
public static SmallPrimeFactorViews EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator, AcceleratorStream stream) public static SmallPrimeFactorViews GetSmallPrimeFactorTables() => new(
Kernels.SmallPrimeFactorsPrimes!,
Kernels.SmallPrimeFactorsSquares!
);
private static SmallPrimeFactorViews EnsureSmallPrimeFactorTables(KernelContainer kernels, AcceleratorStream stream)
{ {
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{ {
return new SmallPrimeFactorViews(primeBuffer, squareBuffer, (int)primeBuffer.Length); return new SmallPrimeFactorViews(primeBuffer, squareBuffer);
} }
Accelerator accelerator = SharedGpuContext.Accelerator;
var hostPrimes = PrimesGenerator.SmallPrimes; var hostPrimes = PrimesGenerator.SmallPrimes;
var hostSquares = PrimesGenerator.SmallPrimesPow2; var hostSquares = PrimesGenerator.SmallPrimesPow2;
var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length); int length = hostPrimes.Length;
var devicePrimes = accelerator.Allocate1D<uint>(length);
devicePrimes.View.CopyFromCPU(stream, hostPrimes); devicePrimes.View.CopyFromCPU(stream, hostPrimes);
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length); var deviceSquares = accelerator.Allocate1D<ulong>(length);
deviceSquares.View.CopyFromCPU(stream, hostSquares); deviceSquares.View.CopyFromCPU(stream, hostSquares);
kernels.SmallPrimeFactorsPrimes = devicePrimes; kernels.SmallPrimeFactorsPrimes = devicePrimes;
kernels.SmallPrimeFactorsSquares = deviceSquares; kernels.SmallPrimeFactorsSquares = deviceSquares;
return new SmallPrimeFactorViews(devicePrimes, deviceSquares, hostPrimes.Length); return new SmallPrimeFactorViews(devicePrimes, deviceSquares);
} }
public static GpuKernelLease Rent() public static GpuKernelLease Rent()
{ {
GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = GpuContextPool.Rent(); var stream = SharedGpuContext.Accelerator.CreateStream();
var stream = gpu.Accelerator.CreateStream(); return GpuKernelLease.Rent(stream, Kernels);
var kernels = GetKernels(gpu.Accelerator, stream);
return GpuKernelLease.Rent(gpu, stream, kernels);
} }
/// <summary> /// <summary>
@@ -130,7 +151,7 @@ public class GpuKernelPool
public static void Run(Action<Accelerator, AcceleratorStream> action) public static void Run(Action<Accelerator, AcceleratorStream> action)
{ {
var lease = Rent(); var lease = Rent();
var accelerator = lease.Accelerator; var accelerator = SharedGpuContext.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
action(accelerator, stream); action(accelerator, stream);
lease.Dispose(); lease.Dispose();
@@ -16,7 +16,7 @@ internal static class GpuStaticTableInitializer
Initialized.GetOrAdd(accelerator, acc => Initialized.GetOrAdd(accelerator, acc =>
{ {
PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(kernels, acc, stream); PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(kernels, acc, stream);
GpuKernelPool.PreloadStaticTables(kernels, acc, stream); GpuKernelPool.PreloadStaticTables(kernels, stream);
PrimeOrderGpuHeuristics.PreloadStaticTables( acc, stream); PrimeOrderGpuHeuristics.PreloadStaticTables( acc, stream);
return 0; return 0;
}); });
@@ -96,7 +96,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong lastProcessed; ulong lastProcessed;
var gpuLease = GpuKernelPool.Rent(); var gpuLease = GpuKernelPool.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = SharedGpuContext.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
// Monitor.Enter(gpuLease.ExecutionLock); // Monitor.Enter(gpuLease.ExecutionLock);
@@ -502,7 +502,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
_owner = owner; _owner = owner;
_lease = GpuKernelPool.Rent(); _lease = GpuKernelPool.Rent();
_accelerator = _lease.Accelerator; _accelerator = SharedGpuContext.Accelerator;
_stream = _lease.Stream; _stream = _lease.Stream;
_capacity = Math.Max(1, owner._gpuBatchSize); _capacity = Math.Max(1, owner._gpuBatchSize);
} }
@@ -15,126 +15,124 @@ public sealed class MersenneNumberDivisorGpuTester
private Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) => private Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
_kernelCache.GetOrAdd(accelerator, acc => _kernelCache.GetOrAdd(accelerator, acc =>
{ {
var loaded = acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel); var loaded = acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>>(); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>>();
}); });
public static void BuildDivisorCandidates() public static void BuildDivisorCandidates()
{ {
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2]; (ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
ulong cycle; ulong cycle;
int count = 0, i, snapshotLength = snapshot.Length; int count = 0, i, snapshotLength = snapshot.Length;
for (i = 3; i < snapshotLength; i += 2) for (i = 3; i < snapshotLength; i += 2)
{ {
cycle = snapshot[i]; cycle = snapshot[i];
if (cycle == 0U) if (cycle == 0U)
{ {
continue; continue;
} }
list[count++] = ((ulong)i, (uint)cycle); list[count++] = ((ulong)i, (uint)cycle);
} }
_divisorCandidates = count == 0 ? [] : list[..count]; _divisorCandidates = count == 0 ? [] : list[..count];
} }
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor) public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{ {
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1)); var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
// There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0] // There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0]
// resultBuffer.MemSetToZero(stream); // resultBuffer.MemSetToZero(stream);
kernel(stream, 1, exponent, divisor, resultBuffer.View); kernel(stream, 1, exponent, divisor, resultBuffer.View);
Span<byte> result = stackalloc byte[1]; Span<byte> result = stackalloc byte[1];
resultBuffer.View.CopyToCPU(stream, ref result[0], 1); resultBuffer.View.CopyToCPU(stream, ref result[0], 1);
stream.Synchronize(); stream.Synchronize();
bool divisible = result[0] != 0; bool divisible = result[0] != 0;
stream.Dispose(); stream.Dispose();
gpu.Dispose(); return divisible;
return divisible; }
}
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>(); private static (ulong divisor, uint cycle)[]? _divisorCandidates = [];
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted) public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
{ {
ReadOnlyGpuUInt128 readOnlyDivisor; ReadOnlyGpuUInt128 readOnlyDivisor;
if (d != UInt128.Zero) if (d != UInt128.Zero)
{ {
readOnlyDivisor = new ReadOnlyGpuUInt128(d); readOnlyDivisor = new ReadOnlyGpuUInt128(d);
if (IsDivisible(p, in readOnlyDivisor)) if (IsDivisible(p, in readOnlyDivisor))
{ {
divisorsExhausted = true; divisorsExhausted = true;
return false; return false;
} }
divisorsExhausted = false; divisorsExhausted = false;
return true; return true;
} }
if (_divisorCandidates is { Length: > 0 } candidates) if (_divisorCandidates is { Length: > 0 } candidates)
{ {
int candidateCount = candidates.Length; int candidateCount = candidates.Length;
for (int index = 0; index < candidateCount; index++) for (int index = 0; index < candidateCount; index++)
{ {
(ulong candidateDivisor, uint cycle) = candidates[index]; (ulong candidateDivisor, uint cycle) = candidates[index];
if (p % cycle != 0UL) if (p % cycle != 0UL)
{ {
continue; continue;
} }
readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors. readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors.
if (IsDivisible(p, in readOnlyDivisor)) if (IsDivisible(p, in readOnlyDivisor))
{ {
divisorsExhausted = true; divisorsExhausted = true;
return false; return false;
} }
} }
} }
UInt128 kMul2; UInt128 kMul2;
var divisorCycles = MersenneDivisorCycles.Shared; var divisorCycles = MersenneDivisorCycles.Shared;
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1); UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
ulong limit = divisorCyclesSearchLimit; ulong limit = divisorCyclesSearchLimit;
if ((UInt128)limit > maxK2) if ((UInt128)limit > maxK2)
{ {
limit = (ulong)maxK2; limit = (ulong)maxK2;
} }
for (ulong k2 = 1UL; k2 <= limit; k2++) for (ulong k2 = 1UL; k2 <= limit; k2++)
{ {
kMul2 = (UInt128)k2 << 1; kMul2 = (UInt128)k2 << 1;
UInt128 candidate = checked(kMul2 * p); UInt128 candidate = checked(kMul2 * p);
d = checked(candidate + UInt128.One); d = checked(candidate + UInt128.One);
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One) if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
{ {
continue; continue;
} }
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d); UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
if ((UInt128)p % cycle128 != UInt128.Zero) if ((UInt128)p % cycle128 != UInt128.Zero)
{ {
continue; continue;
} }
readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors. readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors.
if (IsDivisible(p, in readOnlyDivisor)) if (IsDivisible(p, in readOnlyDivisor))
{ {
divisorsExhausted = true; divisorsExhausted = true;
return false; return false;
} }
} }
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2; divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
return true; return true;
} }
} }
@@ -17,7 +17,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder})."); throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
var gpuLease = GpuKernelPool.Rent(); var gpuLease = GpuKernelPool.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = SharedGpuContext.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
@@ -31,11 +31,11 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
ulong step3 = ((exponent % 3UL) << 1) % 3UL; ulong step3 = ((exponent % 3UL) << 1) % 3UL;
ulong step5 = ((exponent % 5UL) << 1) % 5UL; ulong step5 = ((exponent % 5UL) << 1) % 5UL;
GpuUInt128 twoPGpu = (GpuUInt128)twoP; GpuUInt128 twoPGpu = (GpuUInt128)twoP;
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator, stream); var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice();
ResiduePrimeViews primeViews = default; ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuLease.Kernels, accelerator, stream); primeViews = GpuKernelPool.GetSmallPrimesOnDevice();
} }
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
@@ -97,8 +97,7 @@ public class MersenneNumberLucasLehmerGpuTester
} }
else else
{ {
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch. var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
@@ -109,7 +108,6 @@ public class MersenneNumberLucasLehmerGpuTester
result = buffer.GetAsArray1D()[0].IsZero; result = buffer.GetAsArray1D()[0].IsZero;
buffer.Dispose(); buffer.Dispose();
stream.Dispose(); stream.Dispose();
gpu.Dispose();
} }
GpuPrimeWorkLimiter.Release(); GpuPrimeWorkLimiter.Release();
@@ -132,8 +130,7 @@ public class MersenneNumberLucasLehmerGpuTester
} }
} }
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetBatchKernel(accelerator); var kernel = GetBatchKernel(accelerator);
@@ -166,7 +163,6 @@ public class MersenneNumberLucasLehmerGpuTester
gpuUInt128Pool.Return(modulusArray); gpuUInt128Pool.Return(modulusArray);
ulongPool.Return(expArray); ulongPool.Return(expArray);
stream.Dispose(); stream.Dispose();
gpu.Dispose();
} }
public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results) public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results)
@@ -189,10 +185,10 @@ public class MersenneNumberLucasLehmerGpuTester
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu) private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
{ {
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var addKernel = GetAddSmallKernel(accelerator);
var addKernel = GetAddSmallKernel(accelerator);
var subKernel = GetSubSmallKernel(accelerator); var subKernel = GetSubSmallKernel(accelerator);
var reduceKernel = GetReduceKernel(accelerator); var reduceKernel = GetReduceKernel(accelerator);
var zeroKernel = GetIsZeroKernel(accelerator); var zeroKernel = GetIsZeroKernel(accelerator);
@@ -240,7 +236,6 @@ public class MersenneNumberLucasLehmerGpuTester
resultBuffer.Dispose(); resultBuffer.Dispose();
stateBuffer.Dispose(); stateBuffer.Dispose();
stream.Dispose(); stream.Dispose();
gpu.Dispose();
return isPrime; return isPrime;
} }
@@ -10,7 +10,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
var gpuLease = GpuKernelPool.Rent(); var gpuLease = GpuKernelPool.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = SharedGpuContext.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One; UInt128 kStart = UInt128.One;
@@ -30,7 +30,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
int currentSize; int currentSize;
int found = 0; int found = 0;
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator, stream); var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice();
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + 1UL; remaining = maxK - kStart + 1UL;
@@ -14,7 +14,6 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberResidueGpuTester(bool useGpuOrder) public class MersenneNumberResidueGpuTester(bool useGpuOrder)
{ {
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
private readonly ConcurrentBag<GpuContextLease> _acceleratorPool = new();
private readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>> _pow2ModKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>> _pow2ModKernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<ResidueResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<ResidueResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
@@ -28,15 +27,14 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpuLease = RentAccelerator(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpuLease.Accelerator;
// Monitor.Enter(gpuLease.ExecutionLock); // Monitor.Enter(gpuLease.ExecutionLock);
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); var resources = RentResources(accelerator, GpuConstants.ScanBatchSize);
var gpuKernels = GpuKernelPool.GetKernels(accelerator, stream); var gpuKernels = GpuKernelPool.Kernels;
var orderBuffer = resources.OrderBuffer; var orderBuffer = resources.OrderBuffer;
ulong[] orderArray = resources.OrderArray; ulong[] orderArray = resources.OrderArray;
@@ -44,8 +42,8 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// Ensure device has small cycles and primes tables for in-kernel lookup // Ensure device has small cycles and primes tables for in-kernel lookup
// TODO: Pass-in the stream down the path // TODO: Pass-in the stream down the path
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuKernels, accelerator, stream); var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice();
ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuKernels, accelerator, stream); ResiduePrimeViews primeViews = GpuKernelPool.GetSmallPrimesOnDevice();
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
UInt128 limit = maxK + UInt128.One; UInt128 limit = maxK + UInt128.One;
@@ -114,7 +112,6 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
stream.Dispose(); stream.Dispose();
// Monitor.Exit(gpuLease.ExecutionLock); // Monitor.Exit(gpuLease.ExecutionLock);
gpuKernels.Dispose(); gpuKernels.Dispose();
ReturnAccelerator(gpuLease);
GpuPrimeWorkLimiter.Release(); GpuPrimeWorkLimiter.Release();
} }
@@ -130,18 +127,6 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
loaded.Method); loaded.Method);
}); });
private GpuContextLease RentAccelerator()
{
if (_acceleratorPool.TryTake(out var lease))
{
return lease;
}
return GpuContextPool.Rent();
}
private void ReturnAccelerator(GpuContextLease lease) => _acceleratorPool.Add(lease);
private ResidueResources RentResources(Accelerator accelerator, int capacity) private ResidueResources RentResources(Accelerator accelerator, int capacity)
{ {
var bag = _resourcePools.GetOrAdd(accelerator, static _ => []); var bag = _resourcePools.GetOrAdd(accelerator, static _ => []);
+6 -23
View File
@@ -504,8 +504,7 @@ public static class NttGpuMath
{ {
int n = values.Length; int n = values.Length;
int bits = (int)Math.Log2(n); int bits = (int)Math.Log2(n);
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var kernel = GetBitReverseKernel(accelerator); var kernel = GetBitReverseKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ThreadStaticPools.GpuUInt128Pool;
var array = pool.Rent(n); var array = pool.Rent(n);
@@ -520,7 +519,6 @@ public static class NttGpuMath
pool.Return(array, clearArray: true); pool.Return(array, clearArray: true);
buffer.Dispose(); buffer.Dispose();
stream.Dispose(); stream.Dispose();
gpu.Dispose();
} }
@@ -568,8 +566,7 @@ public static class NttGpuMath
private static void ForwardGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void ForwardGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = (int)values.Length; int n = (int)values.Length;
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetForwardKernel(accelerator); var kernel = GetForwardKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ThreadStaticPools.GpuUInt128Pool;
@@ -592,8 +589,6 @@ public static class NttGpuMath
outputBuffer.Dispose(); outputBuffer.Dispose();
inputBuffer.Dispose(); inputBuffer.Dispose();
stream.Dispose(); stream.Dispose();
gpu.Dispose();
} }
public static void Inverse(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) public static void Inverse(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
@@ -667,8 +662,7 @@ public static class NttGpuMath
private static void InverseGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void InverseGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = values.Length; int n = values.Length;
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetInverseKernel(accelerator); var kernel = GetInverseKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ThreadStaticPools.GpuUInt128Pool;
@@ -710,15 +704,12 @@ public static class NttGpuMath
outputBuffer.Dispose(); outputBuffer.Dispose();
inputBuffer.Dispose(); inputBuffer.Dispose();
stream.Dispose(); stream.Dispose();
gpu.Dispose();
} }
private static void ForwardGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void ForwardGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = values.Length; int n = values.Length;
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var buffer = accelerator.Allocate1D<GpuUInt128>(n); var buffer = accelerator.Allocate1D<GpuUInt128>(n);
buffer.View.CopyFromCPU(stream, ref values[0], n); buffer.View.CopyFromCPU(stream, ref values[0], n);
@@ -728,15 +719,12 @@ public static class NttGpuMath
stream.Synchronize(); stream.Synchronize();
stream.Dispose(); stream.Dispose();
buffer.Dispose(); buffer.Dispose();
gpu.Dispose();
} }
private static void InverseGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void InverseGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = values.Length; int n = values.Length;
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var buffer = accelerator.Allocate1D<GpuUInt128>(n); var buffer = accelerator.Allocate1D<GpuUInt128>(n);
buffer.View.CopyFromCPU(stream, ref values[0], n); buffer.View.CopyFromCPU(stream, ref values[0], n);
@@ -746,8 +734,6 @@ public static class NttGpuMath
stream.Synchronize(); stream.Synchronize();
stream.Dispose(); stream.Dispose();
buffer.Dispose(); buffer.Dispose();
gpu.Dispose();
} }
private static void ForwardDevice(Accelerator accelerator, AcceleratorStream stream, ArrayView<GpuUInt128> data, int length, GpuUInt128 modulus, SquareCacheEntry cache) private static void ForwardDevice(Accelerator accelerator, AcceleratorStream stream, ArrayView<GpuUInt128> data, int length, GpuUInt128 modulus, SquareCacheEntry cache)
@@ -939,8 +925,7 @@ public static class NttGpuMath
public static void PointwiseMultiply(Span<GpuUInt128> left, Span<GpuUInt128> right, GpuUInt128 modulus) public static void PointwiseMultiply(Span<GpuUInt128> left, Span<GpuUInt128> right, GpuUInt128 modulus)
{ {
var gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetMulKernel(accelerator); var kernel = GetMulKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ThreadStaticPools.GpuUInt128Pool;
@@ -963,8 +948,6 @@ public static class NttGpuMath
rightBuffer.Dispose(); rightBuffer.Dispose();
leftBuffer.Dispose(); leftBuffer.Dispose();
stream.Dispose(); stream.Dispose();
gpu.Dispose();
} }
private static ulong[] GenerateSmallPrimes(int limit) private static ulong[] GenerateSmallPrimes(int limit)
@@ -199,7 +199,7 @@ internal static partial class PrimeOrderGpuHeuristics
try try
{ {
var lease = GpuKernelPool.Rent(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetPartialFactorKernel(accelerator); var kernel = GetPartialFactorKernel(accelerator);
@@ -366,7 +366,7 @@ internal static partial class PrimeOrderGpuHeuristics
order = 0UL; order = 0UL;
var lease = GpuKernelPool.Rent(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetOrderKernel(accelerator); var kernel = GetOrderKernel(accelerator);
@@ -527,7 +527,7 @@ internal static partial class PrimeOrderGpuHeuristics
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results) private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
{ {
var lease = GpuKernelPool.Rent(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
// TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity. // TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity.
@@ -586,7 +586,7 @@ internal static partial class PrimeOrderGpuHeuristics
try try
{ {
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetPow2ModWideKernel(accelerator); var kernel = GetPow2ModWideKernel(accelerator);
// TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity. // TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity.
@@ -6,12 +6,11 @@ namespace PerfectNumbers.Core.Gpu;
public readonly struct SmallPrimeFactorViews( public readonly struct SmallPrimeFactorViews(
MemoryBuffer1D<uint, Stride1D.Dense> primes, MemoryBuffer1D<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares, MemoryBuffer1D<ulong, Stride1D.Dense> squares)
int count)
{ {
public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes = primes; public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes = primes;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares = squares; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares = squares;
public readonly int Count = count; public readonly int Count = (int)primes.Length;
public ArrayView1D<uint, Stride1D.Dense> PrimesView public ArrayView1D<uint, Stride1D.Dense> PrimesView
{ {
+1 -1
View File
@@ -805,7 +805,7 @@ public class MersenneDivisorCycles
divisors = pool.Rent(count); divisors = pool.Rent(count);
outCycles = pool.Rent(count); outCycles = pool.Rent(count);
var lease = GpuKernelPool.Rent(); var lease = GpuKernelPool.Rent();
var accelerator = lease.Accelerator; var accelerator = SharedGpuContext.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
var loaded = accelerator.LoadAutoGroupedStreamKernel< var loaded = accelerator.LoadAutoGroupedStreamKernel<
Index1D, Index1D,
+1 -1
View File
@@ -148,7 +148,7 @@ public sealed class MersenneNumberTester(
UInt128[] qs = qsBuffer; UInt128[] qs = qsBuffer;
var gpuLease = GpuKernelPool.Rent(); var gpuLease = GpuKernelPool.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = SharedGpuContext.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
var orderKernel = gpuLease.OrderKernel!; var orderKernel = gpuLease.OrderKernel!;
@@ -20,13 +20,13 @@ internal static partial class PrimeOrderCalculator
remaining = value; remaining = value;
var lease = GpuKernelPool.Rent(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
KernelContainer kernels = lease.Kernels; KernelContainer kernels = lease.Kernels;
ScratchBuffer scratch = GpuScratchBufferPool.Rent(accelerator, GpuSmallPrimeFactorSlots, 0); ScratchBuffer scratch = GpuScratchBufferPool.Rent(accelerator, GpuSmallPrimeFactorSlots, 0);
SmallPrimeFactorViews tables = GpuKernelPool.EnsureSmallPrimeFactorTables(kernels, accelerator, stream); SmallPrimeFactorViews tables = GpuKernelPool.GetSmallPrimeFactorTables();
if (kernels.SmallPrimeFactorsPrimes is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsPrimes)} is null"); if (kernels.SmallPrimeFactorsPrimes is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsPrimes)} is null");
if (kernels.SmallPrimeFactorsSquares is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsSquares)} is null"); if (kernels.SmallPrimeFactorsSquares is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsSquares)} is null");
@@ -101,7 +101,7 @@ internal static partial class PrimeOrderCalculator
} }
GpuKernelLease lease = GpuKernelPool.Rent(); GpuKernelLease lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
+2 -2
View File
@@ -273,7 +273,7 @@ public sealed class PrimeTester
Accelerator accelerator = SharedGpuContext.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = accelerator.CreateStream(); AcceleratorStream stream = accelerator.CreateStream();
KernelContainer kernels = GpuKernelPool.GetKernels(accelerator, stream); KernelContainer kernels = GpuKernelPool.Kernels;
GpuStaticTableInitializer.EnsureStaticTables(kernels, accelerator, stream); GpuStaticTableInitializer.EnsureStaticTables(kernels, accelerator, stream);
@@ -433,7 +433,7 @@ public sealed class PrimeTester
AcceleratorContext = SharedGpuContext.Context; AcceleratorContext = SharedGpuContext.Context;
Accelerator = SharedGpuContext.Accelerator; Accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = SharedGpuContext.Accelerator.CreateStream(); AcceleratorStream stream = SharedGpuContext.Accelerator.CreateStream();
var kernels = GpuKernelPool.GetKernels(Accelerator, stream); var kernels = GpuKernelPool.Kernels;
GpuStaticTableInitializer.EnsureStaticTables(kernels, Accelerator, stream); GpuStaticTableInitializer.EnsureStaticTables(kernels, Accelerator, stream);
_sharedTables = RentSharedTables(Accelerator, stream); _sharedTables = RentSharedTables(Accelerator, stream);
+1 -1
View File
@@ -463,7 +463,7 @@ public static partial class ULongExtensions
GpuKernelLease lease = GpuKernelPool.Rent(); GpuKernelLease lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = SharedGpuContext.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernelGroup = Kernel; var kernelGroup = Kernel;