ILGPU Documentation
repository·master·Indexed 23 days ago
https://github.com/m4rs-mt/ilgpuILGPU is a high-performance JIT compiler for .NET that enables developers to write GPU kernels in C# without native dependencies. It supports multiple accelerator types, including CUDA and a multi-threaded CPU accelerator for debugging. The library provides abstractions for hardware management via Context and Accelerator objects, and handles data movement between Host and Device using MemoryBuffers and ArrayViews. It includes the ILGPU.Algorithms library for high-level operations like sorting and prefix sums.
What's inside ILGPU
- ILGPU is a library that provides a C# interface for GPU programming. It allows you to write C# code that is transformed into OpenCL or PTX (CUDA assembly), enabling you to leverage the performance of CUDA and OpenCL while using the syntax and ease of use of C#.
Overview of ILGPU
masterILGPU is a JIT (just-in-time) compiler for high-performance GPU programs written in .NET-based languages. It is written entirely in C# without native dependencies, providing a bridge between the convenience of C++ AMP and the high performance of CUDA.
Key features include:
- Kernel Flexibility: Functions within kernels are standard C# functions (no special annotations required) and can work on value types.
- CPU Acceleration: All kernels, including those utilizing hardware features like shared memory and atomics, can be executed and debugged on the CPU using an integrated multi-threaded CPU accelerator.
Optimize GPU performance using Struct of Arrays (SoA)
masterWhen designing data structures for GPU kernels in ILGPU, you can choose between two primary memory layouts: Array of Structs (AoS) and Struct of Arrays (SoA).
Array of Structs (AoS)
In AoS, data for a single entity is grouped together in memory. For example, an array of
Particlestructs where each element containspos,vel, andaccel.Memory Layout:
p0: [pos, vel, accel] p1: [pos, vel, accel]Struct of Arrays (SoA)
In SoA, each field of the struct is stored in its own separate array. This allows the GPU to perform "coherent" or "chunked" memory loads, as multiple values of the same type (e.g., all positions) are stored contiguously.
Memory Layout:
pos0, pos1, ... vel0, vel1, ... accel0, accel1, ...Performance Impact
While SoA is more complex to implement and manage, it significantly improves memory throughput on the GPU. For large datasets (e.g., 50,000 particles), switching from AoS to SoA can result in performance gains of up to 5x because the GPU can load data more efficiently.
Understand the difference between CPU (SIMD) and GPU (SIMT) execution
masterWhen programming for a GPU using ILGPU, it is critical to understand that you cannot program it like a CPU. While CPUs use SIMD (Single Instruction Multiple Data) to perform math operations on multiple pieces of data at once, GPUs use SIMT (Single Instruction Multiple Threads).
In SIMT, the GPU assumes that a group of threads (typically 32) will execute the exact same instruction stream. It performs a single fetch and decode for the entire group, then executes the instruction across all threads simultaneously. To write efficient kernels, you must pay close attention to:
- Memory Access
- Data Locality
- Threading
Create and use Accelerators
masterAn
Acceleratorrepresents a specific hardware or software GPU (CPU, CUDA, or OpenCL). Every ILGPU program requires at least one accelerator.Accelerator Types
| Type | Requirements | Best Use Case | | :--- | :--- | : | | CPUAccelerator | None | Debugging and fallback. Allows use of C# debugging features. | | CudaAccelerator | NVIDIA GPU (GTX 680+) | High-performance NVIDIA hardware. | | CLAccelerator | OpenCL 2.0+ capable GPU | AMD or Intel GPUs. |
Creating Accelerators
You can create specific accelerators using the
Contextmethods. The integer parameter denotes the index of the device to use in multi-device systems.- CPU:
context.CreateCPUAccelerator(0);(Requiresusing ILGPU.CPU;) - CUDA:
context.CreateCudaAccelerator(0);(Requiresusing ILGPU.Cuda;) - OpenCL:
context.CreateCLAccelerator(0);(Requiresusing ILGPU.OpenCL;)
Lifecycle and Disposal
Accelerators must be disposed of in the reverse order of their creation. For example, if you create a
Contextand then anAccelerator, you must dispose of theAcceleratorbefore theContextis disposed.Deviceinstances do not require disposal.- CPU:
How implicitly grouped kernels work
masterImplicitly grouped kernels provide a high-level programming model where ILGPU manages the thread grid and group sizes automatically.
Key Characteristics:
- Abstraction: The details of kernel invocation (group sizes, thread participation) are hidden from the user.
- Limitations: Because group sizes are managed by ILGPU, you must not use shared memory, group functionality, or warp-specific intrinsics in these kernels.
- Usage: Use the first parameter as an index type (
Index1D,Index2D, orIndex3D) to access the global index of the thread.
class ... { static void ImplicitlyGrouped_Kernel( [Index1D|Index2D|Index3D] index, [Kernel Parameters]...) { // Use the index parameter to access the global index of i-th thread in the global thread grid } }class ... { static void ImplicitlyGrouped_Kernel( [Index1D|Index2D|Index3D] index, [Kernel Parameters]...) { // Kernel code // Use the index parameter to access the global index of i-th thread in the global thread grid } }Manage memory with MemoryBuffer
masterMemoryBufferrepresents an allocated memory region (an array) of a specific value type on an accelerator.Key usage rules:
- Manual Disposal: While
MemoryBufferinstances are eventually released by the Garbage Collector or when the parentAcceleratoris disposed, you should manually dispose of them usingusingblocks or.Dispose()to ensure immediate and explicit control over GPU memory. - Kernel Usage: You cannot pass a
MemoryBufferdirectly to a kernel. Instead, you must pass anArrayViewthat points to the buffer's memory region. - Dimensions: ILGPU provides built-in support for 1D, 2D, and 3D buffers. nD-buffers can also be managed using custom index types.
// Allocate a memory buffer on the current accelerator device. using (var buffer = accelerator.Allocate1D<int>(1024)) { // Perform operations using views... } // Dispose the buffer after performing all operations- Manual Disposal: While
Understand Backends and IRContext
masterBackends
A
Backendrepresents target-specific code-generation functionality for a specific device. While you can use them to manually compile kernels, you do not need to create custom backend instances when using the standard ILGPU runtime;Acceleratorinstances already carry configured backends.IRContext
An
IRContextmanages and caches intermediate-representation (IR) code for reuse during compilation.- It is not tied to a specific
Backendand can be reused across different hardware architectures. - A main ILGPU
Contextalready has an associatedIRContextused for high-level kernel loading, so manual management is usually unnecessary.
- It is not tied to a specific
Organize the documentation section
masterThe documentation section is automatically generated from the
/Docsfolder. To maintain a clean and predictable structure, follow these rules:- Index: The
/Docsfolder must contain aReadMe.mdfile, which serves as the section index. - Naming: Markdown filenames should only contain letters, digits, and hyphens.
- Grouping: Use sub-folders to group files. Note that only 1-depth sub-folders are supported for sections.
- Ordering: Prefix filenames or sub-folders with
DD_(whereDis a digit) to control the display order (e.g.,01_intro.md,02_advanced.md).
- Index: The
Use Dynamic Specialization for optimized kernels
masterDynamic specialization allows you to define kernels with constant values that are not known until runtime. This enables the compiler to perform optimizations like constant propagation and loop unrolling that are otherwise impossible with standard parameters.
How it works:
- Use the
SpecializedValue<T>type for kernel parameters. - The kernel is precompiled during loading.
- A final compilation step occurs during the first call with a new, non-cached
SpecializedValue<T>combination. - Subsequent calls with the same value use the cached specialized instance.
Requirement: Values passed via
SpecializedValue<T>must implement theIEquatableinterface to ensure correct caching.Example:
// Standard kernel: 'c' is a runtime variable static void GenericKernel(ArrayView<int> data, int c) { var globalIndex = Grid.GlobalIndex.X; data[globalIndex] = c + 2; } // Specialized kernel: 'c' is treated as an inlined constant static void SpecializedKernel(ArrayView<int> data, SpecializedValue<int> c) { var globalIndex = Grid.GlobalIndex.X; data[globalIndex] = c + 2; } // Launching // Generic launch var genericKernel = accl.LoadStreamKernel<ArrayView<int>, int>(GenericKernel); genericKernel((<GridDim>, <GroupDim>), buffer.View, 40); // Specialized launch var specializedKernel = accl.LoadStreamKernel<ArrayView<int>, SpecializedValue<int>>(SpecializedKernel); specializedKernel((<GridDim>, <GroupDim>), buffer.View, SpecializedValue.New(40));class ... { static void GenericKernel(ArrayView<int> data, int c) { var globalIndex = Grid.GlobalIndex.X; // Generates code that loads <i>c</i> and adds the value <i>2</i> at runtime of the kernel data[globalIndex] = c + 2; } static void SpecializedKernel(ArrayView<int> data, SpecializedValue<int> c) { var globalIndex = Grid.GlobalIndex.X; // Generates code that has an inlined constant value data[globalIndex] = c + 2; // Will be specialized for every value <i>c</i> } static void ...(...) { using var context = new Context(); using var accl = new CudaAccelerator(context); var genericKernel = accl.LoadStreamKernel<ArrayView<int>, int>(GenericKernel); ... genericKernel((<GridDim>, <GroupDim>), buffer.View, 40); var specializedKernel = accl.LoadStreamKernel<ArrayView<int>, SpecializedValue<int>>(GenericKernel); ... specializedKernel((<GridDim>, <GroupDim>), buffer.View, SpecializedValue.New(40)); ... } }- Use the
Assign names to Cuda/OpenCL kernels
masterYou can use either the .NET function name or a custom name as the entry point for Cuda/OpenCL kernels. This makes profiling and debugging easier by ensuring multiple kernels have distinct names in the output.
Constraint: Custom kernel names must consist of ASCII characters only. Any other characters will be automatically mapped to
_in the assembly code.Choose the appropriate math class for GPU operations
masterTo ensure operations are performed on 32-bit floats on GPU hardware rather than 64-bit doubles, choose the correct math class based on your needs:
IntrinsicMath: Provides basic math operations that are supported across all target platforms.XMath(available in the algorithms library): Provides support for all common 32-bit float and 64-bit float math operations.
Using these instead of the standard .NET
Mathclass helps avoid unnecessary 64-bit precision overhead.