Skip to content

Commit 6c184b2

Browse files
committed
Added option to reserve GPU for long-term exclusive use together with the new example showing the functionality.
1 parent e50675c commit 6c184b2

File tree

4 files changed

+271
-18
lines changed

4 files changed

+271
-18
lines changed

PPU4ILGPU/GPUAllocator.cs

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,21 @@ public enum Mode
6262
}
6363

6464
/// <summary>
65-
/// Represents a pairing of a GPU accelerator with the number of jobs currently booked on it.
65+
/// Holds runtime information about a GPU accelerator, including the number of jobs booked and whether it is reserved for exclusive use.
6666
/// </summary>
67-
/// <remarks>This class is used to track the association between a GPU accelerator and the number
68-
/// of jobs assigned to it. It provides properties to access the accelerator and manage the job count.</remarks>
69-
private class AccelJobsPair
67+
/// <remarks>This class is used internally to track the state of a GPU accelerator, including the
68+
/// number of jobs currently booked and whether the accelerator is reserved for exclusive use.</remarks>
69+
private class AccelRTI
7070
{
7171
internal GPUWrappedAccelerator WrappedAccel { get; }
7272
internal int NumOfBookedJobs { get; set; }
73+
internal bool Reserved { get; set; }
7374

74-
internal AccelJobsPair(GPUWrappedAccelerator wrappedAccel)
75+
internal AccelRTI(GPUWrappedAccelerator wrappedAccel)
7576
{
7677
WrappedAccel = wrappedAccel;
7778
NumOfBookedJobs = 0;
79+
Reserved = false;
7880
}
7981

8082
}
@@ -93,8 +95,8 @@ internal AccelJobsPair(GPUWrappedAccelerator wrappedAccel)
9395
private readonly object _syncRoot;
9496
private Mode _mode;
9597
private readonly Context _context;
96-
private readonly List<AccelJobsPair> _GPUs;
97-
private readonly AccelJobsPair? _CPU;
98+
private readonly List<AccelRTI> _GPUs;
99+
private readonly AccelRTI? _CPU;
98100
private int _isDisposed;
99101

100102
/// <summary>
@@ -184,7 +186,77 @@ public Mode CurrentMode()
184186
public List<GPUWrappedAccelerator> GetAvailableGPUs()
185187
{
186188
ObjectDisposedException.ThrowIf(_isDisposed != 0, this);
187-
return _GPUs.Select(x => x.WrappedAccel).ToList();
189+
lock (_syncRoot)
190+
{
191+
// Return a list of available GPUs that are not reserved
192+
return [.. from item in _GPUs where !item.Reserved select item.WrappedAccel];
193+
}
194+
}
195+
196+
private List<AccelRTI> GetAvailableAccelRTIs()
197+
{
198+
// Return a list of AccelRTI that are not reserved
199+
return [.. from item in _GPUs where !item.Reserved select item];
200+
}
201+
202+
/// <summary>
203+
/// Attempts to reserve the specified GPU accelerator for long-term exclusive use.
204+
/// </summary>
205+
/// <remarks>This method is thread-safe and ensures that only one thread can reserve a GPU
206+
/// accelerator at a time.</remarks>
207+
/// <param name="accel">The GPU accelerator to reserve. Must be an instance of <see cref="GPUWrappedAccelerator"/> that exists in
208+
/// the list of available GPU accelerators.</param>
209+
/// <returns><see langword="true"/> if the GPU accelerator was successfully reserved; otherwise, <see langword="false"/>
210+
/// if the GPU accelerator was already reserved.</returns>
211+
/// <exception cref="ArgumentException">Thrown if <paramref name="accel"/> is not found in the list of available GPU accelerators.</exception>
212+
public bool ReserveGPU(GPUWrappedAccelerator accel)
213+
{
214+
ObjectDisposedException.ThrowIf(_isDisposed != 0, this);
215+
lock (_syncRoot)
216+
{
217+
int idx = _GPUs.FindIndex(x => x.WrappedAccel == accel);
218+
if (idx < 0)
219+
{
220+
// GPU accelerator not found
221+
throw new ArgumentException("GPU accelerator not found in the list of GPU accelerators.", nameof(accel));
222+
}
223+
if (_GPUs[idx].Reserved)
224+
{
225+
// Already reserved
226+
return false;
227+
}
228+
_GPUs[idx].Reserved = true;
229+
return true;
230+
}
231+
}
232+
233+
/// <summary>
234+
/// Releases the reservation on the specified GPU accelerator, if it is currently reserved.
235+
/// </summary>
236+
/// <remarks>This method ensures thread safety by locking access to the internal GPU list during
237+
/// the operation.</remarks>
238+
/// <param name="accel">The GPU accelerator to release the reservation for.</param>
239+
/// <returns><see langword="true"/> if the reservation was successfully released; otherwise, <see langword="false"/> if
240+
/// the GPU accelerator was not reserved.</returns>
241+
/// <exception cref="ArgumentException">Thrown if <paramref name="accel"/> is not found in the list of GPU accelerators.</exception>
242+
public bool ReleaseReservedGPU(GPUWrappedAccelerator accel)
243+
{
244+
ObjectDisposedException.ThrowIf(_isDisposed != 0, this);
245+
lock (_syncRoot)
246+
{
247+
int idx = _GPUs.FindIndex(x => x.WrappedAccel == accel);
248+
if (idx < 0)
249+
{
250+
// GPU accelerator not found
251+
throw new ArgumentException("GPU accelerator not found in the list of GPU accelerators.", nameof(accel));
252+
}
253+
if (!_GPUs[idx].Reserved)
254+
{
255+
return false; // Not reserved
256+
}
257+
_GPUs[idx].Reserved = false;
258+
return true;
259+
}
188260
}
189261

190262
private void IncNumOfBookedJobs()
@@ -228,7 +300,10 @@ private void IncNumOfBookedJobs()
228300
}
229301
return _CPU?.WrappedAccel;
230302
}
231-
else if (_GPUs.Count == 0)
303+
//Get available GPU accelerators
304+
List<AccelRTI> availableAccels = GetAvailableAccelRTIs();
305+
// If no accelerators are available, return null
306+
if (availableAccels.Count == 0)
232307
{
233308
// No GPU accelerators available
234309
return null;
@@ -241,17 +316,17 @@ private void IncNumOfBookedJobs()
241316
}
242317
else if (_mode == Mode.LeastPowerfulGPU)
243318
{
244-
idx = _GPUs.Count - 1;
319+
idx = availableAccels.Count - 1;
245320
}
246321
else
247322
{
248323
// Standard mode
249324
int minNumOfBookedJobs = int.MaxValue;
250-
// Find the GPU with the minimum number of booked jobs
325+
// Find the available GPU with the minimum number of booked jobs
251326
// and if there are multiple ones, select the one with the highest power score
252-
for (int i = 0; i < _GPUs.Count; i++)
327+
for (int i = 0; i < availableAccels.Count; i++)
253328
{
254-
int numOfBookedJobs = _GPUs[i].NumOfBookedJobs;
329+
int numOfBookedJobs = availableAccels[i].NumOfBookedJobs;
255330
if (numOfBookedJobs < minNumOfBookedJobs)
256331
{
257332
minNumOfBookedJobs = numOfBookedJobs;
@@ -260,8 +335,8 @@ private void IncNumOfBookedJobs()
260335
}
261336
}
262337
// Return the selected accelerator
263-
GPUWrappedAccelerator accel = _GPUs[idx].WrappedAccel;
264-
++_GPUs[idx].NumOfBookedJobs;
338+
GPUWrappedAccelerator accel = availableAccels[idx].WrappedAccel;
339+
++availableAccels[idx].NumOfBookedJobs;
265340
IncNumOfBookedJobs();
266341
return accel;
267342
}

PPU4ILGPU/PPU4ILGPU.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<PackageId>PPU4ILGPU</PackageId>
8-
<Version>1.0.0</Version>
8+
<Version>1.1.0</Version>
99
<Authors>okozelsk</Authors>
1010
<Description>Library of parallel processing utils for ILGPU. Offers thread-safe balanced utilization of all available GPU devices on host machine and fast loading of once compiled GPU kernels.</Description>
1111
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using ILGPU;
2+
using ILGPU.Backends.PTX;
3+
using ILGPU.Runtime;
4+
using ILGPU.Runtime.OpenCL;
5+
using PPU4ILGPU;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.Concurrent;
9+
using System.Diagnostics;
10+
using System.Linq;
11+
using System.Text;
12+
using System.Threading.Tasks;
13+
14+
namespace TutorialApp
15+
{
16+
/// <summary>
17+
/// Demonstrates the usage of GPU allocation and reservation mechanisms for parallel processing tasks.
18+
/// </summary>
19+
/// <remarks>This class provides an example of how to use the <see cref="GPUAllocator"/> to manage GPU
20+
/// resources for parallel operations. It shows reserving a specific GPU for exclusive
21+
/// use and releasing it back among available GPUs.</remarks>
22+
public class AllocatorGPUReservationExample
23+
{
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="AllocatorGPUReservationExample"/> class.
27+
/// </summary>
28+
/// <remarks>This constructor sets up the example by configuring the GPU allocation mode to <see
29+
/// cref="GPUAllocator.Mode.Standard"/>, which uses all available GPU devices. It also outputs initialization
30+
/// messages to the console.</remarks>
31+
public AllocatorGPUReservationExample()
32+
{
33+
Console.WriteLine("*******************");
34+
Console.WriteLine("* Example started *");
35+
Console.WriteLine("*******************");
36+
//Set the GPU allocation mode to Standard to use the default GPU allocation mode where all GPU devices are used
37+
GPU.Allocator.SetMode(GPUAllocator.Mode.Standard);
38+
Console.WriteLine();
39+
}
40+
41+
/// <summary>
42+
/// Simulates a job execution, utilizing a GPU accelerator if available, or falling back to CPU processing.
43+
/// </summary>
44+
/// <remarks>If a GPU accelerator is available, it is acquired and used for processing. The method
45+
/// ensures thread safety by locking the GPU accelerator during its usage. If no GPU is available, the job is
46+
/// processed on the CPU. The method logs the processing details, including whether the GPU or CPU was used, and
47+
/// the total execution time.</remarks>
48+
/// <param name="threadName">The name of the thread executing the job. This is used for logging purposes to identify the source of the
49+
/// output.</param>
50+
private static void JobSimulation(string threadName)
51+
{
52+
Stopwatch sw = new();
53+
//Start measuring time
54+
sw.Start();
55+
56+
//Try to acquire a GPU accelerator for processing
57+
GPUWrappedAccelerator? a = GPU.Allocator.Acquire();
58+
if (a == null)
59+
{
60+
// No GPU available, fallback to CPU processing
61+
Console.WriteLine($"[{threadName}] started processing on CPU...");
62+
Thread.Sleep(100); // Simulate some CPU work
63+
}
64+
else
65+
{
66+
// GPU is available, use it for processing
67+
Console.WriteLine($"[{threadName}] started processing on device {a.AccelObj.Device.Name}...");
68+
//Lock the accelerator to ensure thread safety
69+
lock (a.ThreadLock)
70+
{
71+
Thread.Sleep(100); // Simulate some CPU work
72+
GPU.Allocator.Release(a);
73+
}
74+
}
75+
//Stop measuring time
76+
sw.Stop();
77+
Console.WriteLine($"[{threadName}] finished in {sw.ElapsedMilliseconds} ms.");
78+
return;
79+
}
80+
81+
/// <summary>
82+
/// Executes a sequence of six parallel operations, each performing fictive job.
83+
/// </summary>
84+
private void ExecuteParallelSequence()
85+
{
86+
Stopwatch sw = new();
87+
Console.WriteLine();
88+
Console.WriteLine($"Parallel sequence started with {nameof(GPUAllocator)} operation mode {Enum.GetName(typeof(GPUAllocator.Mode), GPU.Allocator.CurrentMode())}...");
89+
// Start measuring time
90+
sw.Start();
91+
// Execute the neighbor sum calculations in parallel
92+
Parallel.Invoke(
93+
() => { JobSimulation($"T1"); },
94+
() => { JobSimulation($"T2"); },
95+
() => { JobSimulation($"T3"); },
96+
() => { JobSimulation($"T4"); },
97+
() => { JobSimulation($"T5"); },
98+
() => { JobSimulation($"T6"); }
99+
);
100+
// Stop measuring time
101+
sw.Stop();
102+
Console.WriteLine($"Parallel sequence completed in {sw.ElapsedMilliseconds} ms.");
103+
return;
104+
}
105+
106+
/// <summary>
107+
/// Executes a sequence of operations utilizing available GPUs, demonstrating GPU allocation, reservation, and
108+
/// release.
109+
/// </summary>
110+
/// <remarks>This method performs the following steps: <list type="number"> <item> Lists all
111+
/// available GPUs, sorted from the most powerful to the least powerful. </item> <item> Executes a parallel
112+
/// sequence using all available GPUs. </item> <item> Attempts to reserve the most powerful GPU for exclusive
113+
/// use and executes another parallel sequence with the remaining GPUs. </item> <item> Releases the reserved GPU
114+
/// and executes a final parallel sequence with all GPUs available again. </item> If no GPUs are available at
115+
/// the start, the method exits early. The method also handles scenarios where GPU reservation or release
116+
/// fails.</remarks>
117+
public void Run()
118+
{
119+
Console.WriteLine("All available GPUs sorted from the most powerfull to the least powerfull");
120+
List<GPUWrappedAccelerator> availableGPUs = GPU.Allocator.GetAvailableGPUs();
121+
foreach (var wa in availableGPUs)
122+
{
123+
Console.WriteLine($"- {wa.AccelObj.Device.Name} ({wa.AccelObj.Device.AcceleratorType})");
124+
}
125+
126+
if(availableGPUs.Count == 0)
127+
{
128+
Console.WriteLine("No GPUs available. Exiting...");
129+
return;
130+
}
131+
132+
Console.WriteLine();
133+
Console.WriteLine("Execution of parallel sequence with all GPUs available");
134+
ExecuteParallelSequence();
135+
136+
Console.WriteLine();
137+
Console.WriteLine("Try to reserve the most powerful GPU for long term...");
138+
GPUWrappedAccelerator mpAccel = availableGPUs[0];
139+
if (!GPU.Allocator.ReserveGPU(mpAccel))
140+
{
141+
Console.WriteLine("Failed to reserve the most powerful GPU. Exiting...");
142+
return;
143+
}
144+
else
145+
{
146+
Console.WriteLine($"{mpAccel.AccelObj.Device.Name} is now reserved for exclusive use, which is preventing {nameof(GPUAllocator)} to allocate it.");
147+
}
148+
149+
Console.WriteLine("Remaining GPUs available");
150+
foreach (var wa in GPU.Allocator.GetAvailableGPUs())
151+
{
152+
Console.WriteLine($"- {wa.AccelObj.Device.Name}");
153+
}
154+
Console.WriteLine("Execution of parallel sequence with remaining GPUs available");
155+
ExecuteParallelSequence();
156+
157+
Console.WriteLine();
158+
Console.WriteLine($"Release reserved {mpAccel.AccelObj.Device.Name}...");
159+
if (!GPU.Allocator.ReleaseReservedGPU(mpAccel))
160+
{
161+
Console.WriteLine("Failed to reserve the most powerful GPU. Exiting...");
162+
return;
163+
}
164+
Console.WriteLine("Execution of parallel sequence with all GPUs available again");
165+
ExecuteParallelSequence();
166+
167+
return;
168+
}
169+
170+
}
171+
}

TutorialApp/Program.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
Console.WriteLine($"1. {nameof(GPUWrappedAccelerator)} used as a standalone component");
1616
Console.WriteLine($"2. {nameof(GPUAllocator)} singleton scenario");
1717
Console.WriteLine($"3. {nameof(GPUStreamPool)} usage of non-default GPU streams");
18-
Console.WriteLine("4. Exit");
19-
Console.WriteLine("Select your choice (press key 1, 2, 3 or 4)...");
18+
Console.WriteLine($"4. {nameof(GPUAllocator)} reservation of {nameof(GPUWrappedAccelerator)} for exclusive use");
19+
Console.WriteLine("5. Exit");
20+
Console.WriteLine("Select your choice (press key 1, 2, 3, 4 or 5)...");
2021
ConsoleKeyInfo keyInfo = Console.ReadKey();
2122
Console.WriteLine();
2223
Console.WriteLine();
@@ -41,6 +42,12 @@
4142
}
4243
break;
4344
case '4':
45+
{
46+
AllocatorGPUReservationExample example = new();
47+
example.Run();
48+
}
49+
break;
50+
case '5':
4451
Console.WriteLine("Exiting...");
4552
exit = true;
4653
break;

0 commit comments

Comments
 (0)