Skip to content

Commit 6e31d83

Browse files
committed
Prepare for public release 1.0.0
1 parent e45e04e commit 6e31d83

36 files changed

Lines changed: 2603 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main, develop ]
8+
9+
jobs:
10+
build:
11+
runs-on: windows-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Setup .NET
20+
uses: actions/setup-dotnet@v4
21+
with:
22+
dotnet-version: '8.x'
23+
24+
- name: Display .NET version
25+
run: dotnet --info
26+
27+
- name: Restore dependencies
28+
run: dotnet restore
29+
30+
- name: Build solution
31+
run: dotnet build --configuration Release --no-restore
32+
33+
- name: Run tests
34+
run: |
35+
if (Test-Path "tests/MultiGpuHelper.Tests") {
36+
dotnet test tests/MultiGpuHelper.Tests/ --configuration Release --no-build --verbosity normal
37+
} else {
38+
Write-Host "Tests directory not found, skipping tests"
39+
}
40+
shell: pwsh
41+
42+
- name: Pack NuGet package
43+
run: dotnet pack src/MultiGpuHelper/MultiGpuHelper.csproj --configuration Release --output ./artifacts
44+
45+
- name: Upload NuGet package artifacts
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: nuget-packages
49+
path: |
50+
./artifacts/*.nupkg
51+
./artifacts/*.snupkg
52+
if-no-files-found: warn

.gitignore

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Build & compilation
2+
bin/
3+
obj/
4+
*.dll
5+
*.exe
6+
*.pdb
7+
8+
# Visual Studio
9+
.vs/
10+
*.user
11+
*.suo
12+
*.cache
13+
*.log
14+
.vsconfig
15+
16+
# Rider / JetBrains
17+
.idea/
18+
*.iml
19+
*.iws
20+
*.ipr
21+
22+
# Visual Studio Code
23+
.vscode/
24+
*.code-workspace
25+
26+
# OS noise
27+
Thumbs.db
28+
Desktop.ini
29+
.DS_Store
30+
31+
# Claude / AI scratch (DO NOT TRACK)
32+
.claude/
33+
.cladue/
34+
.ai/
35+
*.prompt
36+
*.prompt.md
37+
38+
# NuGet
39+
.nuget/
40+
*.nupkg
41+
!MultiGpuHelper.1.0.0.nupkg
42+
!MultiGpuHelper.1.0.0.snupkg
43+
44+
# Misc temp files
45+
*.tmp
46+
*.temp
47+
~*
48+
.swp
49+
.swo
50+
51+
# Keep strong-name key (TRACKED)
52+
# *.snk is tracked intentionally

CHANGELOG.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# CHANGELOG
2+
3+
All notable changes to MultiGpuHelper will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.0.0] – 2024-01-07 – Initial Release
9+
10+
### Added
11+
- **Core GPU Management**
12+
- `GpuManager` for device registry and selection
13+
- `GpuDevice` model with VRAM tracking
14+
- Support for multiple GPU devices
15+
16+
- **GPU Auto-Detection**
17+
- `NvidiaSmiProbeProvider` for automatic NVIDIA GPU detection
18+
- Graceful fallback if nvidia-smi is unavailable
19+
- `IGpuProbeProvider` abstraction for extensibility
20+
21+
- **Device Selection Policies**
22+
- `GpuPolicy.RoundRobin` – distribute work evenly
23+
- `GpuPolicy.MostFreeVram` – select GPU with most available memory
24+
- `GpuPolicy.SpecificDevice` – route to explicit GPU ID
25+
26+
- **VRAM Soft-Budgeting**
27+
- `VramBudget` class with thread-safe reservations
28+
- Per-device VRAM limits
29+
- `TryReserve()` / `Release()` API
30+
- Automatic budget enforcement
31+
32+
- **Concurrency Control**
33+
- Per-GPU semaphores via `SemaphoreSlim`
34+
- `MaxConcurrentJobs` configuration per device
35+
- Thread-safe work dispatching
36+
37+
- **Work Dispatching**
38+
- `GpuDispatcher` for async work scheduling
39+
- Support for async/sync lambdas with/without return values
40+
- Timeout and cancellation token support
41+
- `GpuWorkItem` for work metadata (tags, VRAM requests, priorities)
42+
43+
- **Logging**
44+
- `IGpuLogger` abstraction for custom logging
45+
- `NoOpLogger` default implementation
46+
47+
- **Utilities**
48+
- `Size` helper for human-readable byte formatting (MiB, GiB)
49+
- XML documentation for all public APIs
50+
51+
- **Error Handling**
52+
- `GpuSelectionException` – no suitable GPU found
53+
- `GpuProbeException` – GPU detection failed
54+
- `GpuBudgetExceededException` – VRAM budget exceeded
55+
- Rich context in exception messages
56+
57+
- **NuGet Packaging**
58+
- Auto-generated .nupkg and .snupkg (symbol package)
59+
- Strong-name signing with 2048-bit RSA key
60+
- MIT license metadata
61+
- Package tags: `gpu;cuda;ai;inference;multi-gpu;scheduler`
62+
63+
- **CI/CD**
64+
- GitHub Actions workflow (Windows, .NET 8.x)
65+
- Automated build, test, and pack
66+
- Artifact upload to GitHub
67+
68+
- **Documentation**
69+
- Comprehensive README with quick-start examples
70+
- VERSIONING.md with SemVer policy and release workflow
71+
- MIT LICENSE
72+
73+
- **Sample Applications**
74+
- .NET 8 console sample with async dispatching
75+
- .NET Framework 4.7.2 sample (device selection)
76+
- Hardware verification test for real GPU devices
77+
78+
- **Testing**
79+
- 13 unit tests (xUnit)
80+
- VramBudget functionality tests
81+
- GpuManager selection policy tests
82+
- Hardware test for P4000 GPUs
83+
84+
### Tested On
85+
- **Hardware**: NVIDIA Quadro P4000 (2 units)
86+
- **Frameworks**: .NET 8.0, .NET Standard 2.0, .NET Framework 4.7.2
87+
- **OS**: Windows 10/11
88+
89+
### Known Limitations
90+
- NVIDIA GPU detection only (extensible to AMD ROCm, Intel oneAPI)
91+
- Requires nvidia-smi for auto-detection (gracefully handles missing driver)
92+
- Net472 sample requires .NET Framework SDK (code compiles, may need runtime)
93+
94+
---
95+
96+
## [Unreleased]
97+
98+
### Planned (Future)
99+
- [ ] AMD ROCm probe provider
100+
- [ ] Intel oneAPI probe provider
101+
- [ ] OpenCL support
102+
- [ ] GPU memory profiling hooks
103+
- [ ] Work queue persistence
104+
- [ ] Multi-machine GPU clustering
105+
- [ ] Performance optimizations
106+
- [ ] Linux/macOS support (after AMD/Intel providers)

GenerateSnk.ps1

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Generate strong-name key for MultiGpuHelper
2+
# This uses the .NET Framework StrongNameKeyPair API
3+
4+
param(
5+
[string]$OutputFile = "MultiGpuHelper.snk"
6+
)
7+
8+
try {
9+
Write-Host "Generating strong-name key: $OutputFile"
10+
11+
# Create a temporary RSA key
12+
$tempPath = [System.IO.Path]::GetTempFileName()
13+
14+
# Use a process to call sn.exe if available, otherwise use managed code
15+
$snPath = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\sn.exe"
16+
17+
if (Test-Path $snPath) {
18+
Write-Host "Using sn.exe from SDK"
19+
& "$snPath" -k $OutputFile
20+
} else {
21+
# Fallback: Create using .NET Reflection Emit with cryptography
22+
# Generate RSA key using managed API
23+
$cspParams = New-Object System.Security.Cryptography.CspParameters
24+
$cspParams.ProviderType = 1 # PROV_RSA_FULL
25+
$cspParams.KeyNumber = 0 # AT_SIGNATURE
26+
27+
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider(2048, $cspParams)
28+
29+
# Export key pair in PUBLICKEYBLOB format
30+
$publicKeyBlob = $rsa.ExportCspBlob($false)
31+
$privateKeyBlob = $rsa.ExportCspBlob($true)
32+
33+
# Write SNK file with correct format
34+
# SNK structure: 0xFDF40000 (magic/version) + key data
35+
[byte[]]$snkHeader = @(0xFD, 0xF4, 0x00, 0x00)
36+
37+
$snkBytes = $snkHeader + $privateKeyBlob
38+
[System.IO.File]::WriteAllBytes($OutputFile, $snkBytes)
39+
40+
Write-Host "Generated SNK using managed RSA"
41+
}
42+
43+
if (Test-Path $OutputFile) {
44+
$fileSize = (Get-Item $OutputFile).Length
45+
Write-Host "SUCCESS - Strong-name key generated: $OutputFile"
46+
Write-Host "File size: $fileSize bytes"
47+
exit 0
48+
} else {
49+
Write-Host "ERROR: SNK file was not created"
50+
exit 1
51+
}
52+
}
53+
catch {
54+
Write-Host "ERROR: $_"
55+
Write-Host $_.Exception.Message
56+
exit 1
57+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 MultiGpuHelper Contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MultiGpuHelper.sln

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiGpuHelper", "src\MultiGpuHelper\MultiGpuHelper.csproj", "{1447468B-4C21-43AC-883E-D44B19BCD32D}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsole", "samples\SampleConsole\SampleConsole.csproj", "{451125E5-CDF6-4B78-B4D2-9BE46495D434}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleNetFramework", "samples\SampleNetFramework\SampleNetFramework.csproj", "{7F051D43-9750-4245-BDA8-1DCE9F499B79}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiGpuHelper.Tests", "tests\MultiGpuHelper.Tests\MultiGpuHelper.Tests.csproj", "{092FE4BB-AA4B-41A1-99C1-178A32395AD0}"
15+
EndProject
16+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
17+
EndProject
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HardwareTest", "samples\HardwareTest\HardwareTest.csproj", "{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}"
19+
EndProject
20+
Global
21+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
22+
Debug|Any CPU = Debug|Any CPU
23+
Debug|x64 = Debug|x64
24+
Debug|x86 = Debug|x86
25+
Release|Any CPU = Release|Any CPU
26+
Release|x64 = Release|x64
27+
Release|x86 = Release|x86
28+
EndGlobalSection
29+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
30+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Debug|x64.ActiveCfg = Debug|Any CPU
33+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Debug|x64.Build.0 = Debug|Any CPU
34+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Debug|x86.ActiveCfg = Debug|Any CPU
35+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Debug|x86.Build.0 = Debug|Any CPU
36+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Release|x64.ActiveCfg = Release|Any CPU
39+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Release|x64.Build.0 = Release|Any CPU
40+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Release|x86.ActiveCfg = Release|Any CPU
41+
{1447468B-4C21-43AC-883E-D44B19BCD32D}.Release|x86.Build.0 = Release|Any CPU
42+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Debug|Any CPU.Build.0 = Debug|Any CPU
44+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Debug|x64.ActiveCfg = Debug|Any CPU
45+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Debug|x64.Build.0 = Debug|Any CPU
46+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Debug|x86.ActiveCfg = Debug|Any CPU
47+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Debug|x86.Build.0 = Debug|Any CPU
48+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Release|Any CPU.ActiveCfg = Release|Any CPU
49+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Release|Any CPU.Build.0 = Release|Any CPU
50+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Release|x64.ActiveCfg = Release|Any CPU
51+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Release|x64.Build.0 = Release|Any CPU
52+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Release|x86.ActiveCfg = Release|Any CPU
53+
{451125E5-CDF6-4B78-B4D2-9BE46495D434}.Release|x86.Build.0 = Release|Any CPU
54+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Debug|Any CPU.Build.0 = Debug|Any CPU
56+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Debug|x64.ActiveCfg = Debug|Any CPU
57+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Debug|x64.Build.0 = Debug|Any CPU
58+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Debug|x86.ActiveCfg = Debug|Any CPU
59+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Debug|x86.Build.0 = Debug|Any CPU
60+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Release|Any CPU.ActiveCfg = Release|Any CPU
61+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Release|Any CPU.Build.0 = Release|Any CPU
62+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Release|x64.ActiveCfg = Release|Any CPU
63+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Release|x64.Build.0 = Release|Any CPU
64+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Release|x86.ActiveCfg = Release|Any CPU
65+
{7F051D43-9750-4245-BDA8-1DCE9F499B79}.Release|x86.Build.0 = Release|Any CPU
66+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
68+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Debug|x64.ActiveCfg = Debug|Any CPU
69+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Debug|x64.Build.0 = Debug|Any CPU
70+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Debug|x86.ActiveCfg = Debug|Any CPU
71+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Debug|x86.Build.0 = Debug|Any CPU
72+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
73+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Release|Any CPU.Build.0 = Release|Any CPU
74+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Release|x64.ActiveCfg = Release|Any CPU
75+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Release|x64.Build.0 = Release|Any CPU
76+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Release|x86.ActiveCfg = Release|Any CPU
77+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0}.Release|x86.Build.0 = Release|Any CPU
78+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
80+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Debug|x64.ActiveCfg = Debug|Any CPU
81+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Debug|x64.Build.0 = Debug|Any CPU
82+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Debug|x86.ActiveCfg = Debug|Any CPU
83+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Debug|x86.Build.0 = Debug|Any CPU
84+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
85+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Release|Any CPU.Build.0 = Release|Any CPU
86+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Release|x64.ActiveCfg = Release|Any CPU
87+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Release|x64.Build.0 = Release|Any CPU
88+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Release|x86.ActiveCfg = Release|Any CPU
89+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F}.Release|x86.Build.0 = Release|Any CPU
90+
EndGlobalSection
91+
GlobalSection(SolutionProperties) = preSolution
92+
HideSolutionNode = FALSE
93+
EndGlobalSection
94+
GlobalSection(NestedProjects) = preSolution
95+
{1447468B-4C21-43AC-883E-D44B19BCD32D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
96+
{451125E5-CDF6-4B78-B4D2-9BE46495D434} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
97+
{7F051D43-9750-4245-BDA8-1DCE9F499B79} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
98+
{092FE4BB-AA4B-41A1-99C1-178A32395AD0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
99+
{2A4D9CCE-E9F2-4C06-B4A2-F8219768AA6F} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
100+
EndGlobalSection
101+
EndGlobal

MultiGpuHelper.snk

596 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)