Last active
December 11, 2015 14:58
-
-
Save danielkza/4618017 to your computer and use it in GitHub Desktop.
Automatic Memory Clock tester for NVIDIA cards
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Automatic NVIDIA memory clock tester | |
.DESCRIPTION | |
Using MemTestG80 and MSI Afterburner, this program automatically tests | |
memory clocks to find the highest stable clock possible. | |
MSI Afterburner must be running, and a driver supporting CUDA must be | |
installed (any recent driver should be fine). | |
The default mode is incremental mode, where the first test starts set to | |
a fixed amount over the starting clock (see parameters), and the same | |
increment is repeated until a failure is detected. Then the last increment | |
is reversed, and a smaller increment used (see StepFactor, StepDefault | |
and StepMin parameters). | |
Bisection mode is enable by the TargetClock parameter, which is then used | |
for the first test. Subsequent failures change the tested clocks to something | |
between the target clock and the last successfull test, until the increments | |
get too small. (see TargetClock, StepFactor and StepMin parameters). | |
Each test will run by default for 50 iterations. The final test will use | |
10 times the defined number of iterations for better accuracy (see | |
TestIterations parameter). | |
ATTENTION: If you have a GTX600 series GPU (or newer), then ALL specified | |
clocks are BOOST CLOCKS, *not* absolute clocks. | |
.PARAMETER GPUIndex | |
index of the GPU to test, starting from 0. Can be ommited if only one GPU | |
is present. GPUs can be viewed with the -ListGPUs option. | |
.PARAMETER ListGPUs | |
Only list available GPUs, don't run any tests | |
.PARAMETER Start | |
Base clock/boost (in Mhz) to be used. Can also be 'stock' (default if not | |
specified) or 'current'. | |
.PARAMETER Target | |
Manual target clock/boost (in Mhz). Enables bisection mode, reducing the | |
total number of tests. If not specified the StepDefault parameter will | |
be used for incremental mode instead. | |
.PARAMETER StepFactor | |
Clock/boost step scaling factor, from 0 to 1. After each failed test, the | |
next increment or decrement becomes (LastStep * StepFactor). | |
.PARAMETER Step | |
Default clock/boost step (in Mhz) between tests. Does not apply if a | |
TargetClock is specified. | |
.PARAMETER StepMin | |
Minimum clock/boost step between each test (in Mhz). If after multiple | |
tests the next change would be smaller than StepMin, the current clock is | |
chosen for final testing. If that fails, the clock/boost is then reduced | |
by StepMin until a stable value is found. | |
.PARAMETER Iterations | |
How many iterations of the memory test to run for each clock/boost value. | |
Anything less than 10 iterations is probably too unreliable. | |
.EXAMPLE | |
> MesTestG80_AutoOC.ps1 | |
Runs tests on the primary GPU if the only one, or prompts for one, using | |
default options and incremental mode, starting with stock clocks. | |
.EXAMPLE | |
> MesTestG80_AutoOC.ps1 -GpuIndex 1 -StartClock current -TargetClock 1000 | |
Runs tests on the second GPU, starting with current clocks, targetting | |
1000Mhz using bisection mode. | |
.EXAMPLE | |
> MesTestG80_AutoOC.ps1 -GpuIndex 0 -StartClock current -StepDefault 100 -StepFactor 0.8 -StepMin 1 | |
Runs tests on the primary GPU, starting with current clocks, incrementing | |
100MHz at first, then 80MHz, 64Mhz, 51Mhz, 40Mhz, etc. Testing will | |
continue until the best stable clocks are determined with 1Mhz precision. | |
(which might take a long time) | |
.EXAMPLE | |
> MesTestG80_AutoOC.ps1 -GPUIndex 0 -StartClock 2000 -StepDefault 1 -TestIterations 100 | |
Runs tests on the primary GPU, starting from a known good clock of | |
2000MHz (boost clock if GTX600, absolute clock otherwise), incrementing only | |
1MHz at a time, and running 100 iterations for each test. | |
.NOTES | |
Author: Daniel Miranda (danielkza2@gmail.com) | |
Version: 1.0 | |
License: GPL v2 or later | |
#> | |
Param( | |
[switch]$Help=$false, | |
[switch]$ListGPUs=$false, | |
[Alias("gpu")] | |
[ValidateScript({$_ -ge 0})] | |
[int]$GpuIndex = $null, | |
[Alias("start")] | |
[ValidateScript({ | |
$num = $_ -as [int] | |
return ($num -ne $null -and $num -gt 0) -or ($_ -in 'current','stock') | |
})] | |
[string]$StartClock='stock', | |
[Alias("target")] | |
[ValidateScript({$_ -ge 0})] | |
[int]$TargetClock=0, | |
[ValidateRange(0.0, 1.0)] | |
[float]$StepFactor=0.5, | |
[Alias("step")] | |
[ValidateScript({$_ -gt 0})] | |
[int]$StepDefault=200, | |
[ValidateScript({$_ -gt 0})] | |
[int]$StepMin=10, | |
[Alias("iterations")] | |
[ValidateScript({$_ -gt 0})] | |
[int]$TestIterations=50 | |
) | |
if($Help) | |
{ | |
Get-Help $Script:MyInvocation.MyCommand.Path -Detailed | |
Exit | |
} | |
Set-StrictMode -Version Latest | |
# ============================================================================ # | |
# to Khz | |
$startClockInt = $startClock -as [int] | |
if($startClockInt -ne $null) | |
{ | |
[int]$startClock = $startClockInt * 1000 | |
} | |
$targetClock *= 1000 | |
$stepDefault *= 1000 | |
$stepMin *= 1000 | |
if($stepDefault -lt $stepMin) | |
{ | |
$stepMin = $stepDefault | |
} | |
$memoryStep = 128 | |
# ============================================================================ # | |
$baseFolder = Split-Path -Parent $script:MyInvocation.MyCommand.Path | |
$memTestFolder = $baseFolder | |
# ============================================================================ # | |
try { | |
Add-Type -Path $baseFolder\MSIAfterburner.NET.dll | |
} catch { | |
throw "Failed to load Afterburner interface library" | |
} | |
try { | |
$abMonitor = New-Object MSI.Afterburner.HardwareMonitor | |
$abControl = New-Object MSI.Afterburner.ControlMemory | |
} catch { | |
throw "Failed to communicate with MSI Afterburner" | |
} | |
# ============================================================================ # | |
Function Get-WmiGPUMem($abMonitor) | |
{ | |
$gpuEntries = $abMonitor.GpuEntries | |
$gpuMemories = @(0) * $gpuEntries.Length | |
$videoControllers = Get-WmiObject "Win32_VideoController" | |
# Look through the video controllers, convert their device IDs to a format comparable to Afterburner's, | |
# then select the one that matches the selected GPU | |
foreach($vc in $videoControllers) | |
{ | |
$driver = Get-WmiObject "Win32_PnPSignedDriver" | ? {$_.DeviceID -eq $vc.PNPDeviceID} | |
if($driver.Location -match 'PCI Bus (?<bus>\d+), device (?<device>\d+), function (?<function>\d+)') | |
{ | |
$bus = $Matches['bus'] | |
$dev = $Matches['device'] | |
$fn = $Matches['function'] | |
if($vc.PNPDeviceID -match 'PCI\\([^\\]+)') | |
{ | |
$abDevID = $Matches[1] + ('&BUS_{0}&DEV_{1}&FN_{2}' -f $bus,$dev,$fn) | |
for($i=0; $i -lt $gpuEntries.Length; $i++) | |
{ | |
if($gpuMemories[$i] -ne 0) | |
{ | |
continue | |
} | |
if($abDevID -eq $gpuEntries[$i].GpuID) | |
{ | |
$gpuMemories[$i] = $vc.AdapterRAM / (1024 * 1024) | |
break | |
} | |
} | |
} | |
} | |
} | |
return $gpuMemories | |
} | |
Function Get-GPUMem($abMonitor) | |
{ | |
$gpuMemories = @(0) * $abMonitor.GpuEntries.Length | |
$wmiGpuMemories = $null | |
for($i=0; $i -lt $abMonitor.GpuEntries.Length; $i++) | |
{ | |
$gpuMonitor = $abMonitor.GpuEntries[$i] | |
if($gpuMonitor.MemAmount -gt 0) | |
{ | |
$gpuMemories[$i] = $gpuMonitor.MemAmount / 1024 | |
} | |
else | |
{ | |
if($WmiGPUMemories -eq $null) | |
{ | |
$WmiGPUMemories = Get-WmiGPUMem $abMonitor | |
} | |
$gpuMemories[$i] = $WmiGPUMemories[$i] | |
} | |
} | |
return $gpuMemories | |
} | |
$gpuMemories = Get-GPUMem $abMonitor | |
# ============================================================================ # | |
Function Format-Clock($clock, $flagMemBoost) | |
{ | |
if($flagMemBoost) | |
{ | |
return "{0:+####;-####;0}Mhz" -f [int]($clock / 1000) | |
} | |
return "{0}Mhz" -f [int]($clock / 1000) | |
} | |
Function Print-GPUInfo($abMonitor) | |
{ | |
for($i=0; $i -lt $abMonitor.GpuEntries.Length; $i++) | |
{ | |
$gpuMonitor = $abMonitor.GpuEntries[$i] | |
$gpuControl = $abControl.GpuEntries[$i] | |
$flagMemBoost = [boolean]($gpuControl.Flags -band [MSI.Afterburner.MACM_SHARED_MEMORY_GPU_ENTRY_FLAG]("MEMORY_CLOCK_BOOST")) | |
Write-Host ("GPU #{0}" -f $i) | |
Write-Host (" Device: {0}" -f $gpuMonitor.Device) | |
Write-Host (" Driver: {0}" -f $gpuMonitor.Driver) | |
if($flagMemBoost) | |
{ | |
Write-Host (" Memory Boost Clock (default): {0}" -f (Format-Clock $gpuControl.MemoryClockBoostDef $true)) | |
Write-Host (" Memory Boost Clock (current): {0}" -f (Format-Clock $gpuControl.MemoryClockBoostCur $true)) | |
} | |
else | |
{ | |
Write-Host (" Memory Clock (default): {0}Mhz" -f (Format-Clock $gpuControl.MemoryClockDef $false)) | |
Write-Host (" Memory Clock (current): {0}Mhz" -f (Format-Clock $gpuControl.MemoryClockCur $false)) | |
} | |
Write-Host (" Memory Size: {0}MB" -f $gpuMemories[$i]) | |
} | |
} | |
Print-GPUInfo $abMonitor | |
if($listGPUs) | |
{ | |
Exit | |
} | |
# ============================================================================ # | |
Function Get-GPUIndex($gpuIndex, $gpuCount) | |
{ | |
if($gpuIndex -eq $null) | |
{ | |
if($gpuCount -gt 1) | |
{ | |
$gpuIndex = (Read-Host ("Select a GPU to Test [0-{0}]" -f ($gpuCount-1))) -as [int] | |
} else { | |
$gpuIndex = 0 | |
} | |
} | |
if($gpuIndex-eq $null -or $gpuIndex -lt 0 -or $gpuIndex -ge $gpuCount) | |
{ | |
Throw "Invalid GPU index." | |
} | |
return $gpuIndex | |
} | |
$gpuIndex = Get-GPUIndex $gpuIndex $abMonitor.GpuEntries.Length | |
$gpuMonitor = $abMonitor.GpuEntries[$gpuIndex] | |
$gpuControl = $abControl.GpuEntries[$gpuIndex] | |
$gpuMemory = $gpuMemories[$gpuIndex] | |
if($gpuMemory -le 0) | |
{ | |
$gpuMemory = (Read-Host "Failed to detect selected GPU's memory. Specify memory amount, in MB") -as [int] | |
if($gpuMemory -le 0) | |
{ | |
throw "Invalid memory amount." | |
} | |
} | |
# ============================================================================ # | |
$flagMemBoost = [boolean]($gpuControl.Flags -band [MSI.Afterburner.MACM_SHARED_MEMORY_GPU_ENTRY_FLAG]("MEMORY_CLOCK_BOOST")) | |
$flagMemClock = [boolean]($gpuControl.Flags -band [MSI.Afterburner.MACM_SHARED_MEMORY_GPU_ENTRY_FLAG]("MEMORY_CLOCK")) | |
if(-not $flagMemBoost -and -not $flagMemClock) | |
{ | |
throw "Selected GPU does not support direct clock or boost clock control." | |
} | |
Function Get-GPUMemClock($gpuControl) | |
{ | |
if($flagMemBoost) | |
{ | |
return $gpuControl.MemoryClockBoostCur | |
} | |
return $gpuControl.MemoryClockCur | |
} | |
Function Get-GPUMemDefClock($gpuControl) | |
{ | |
if($flagMemBoost) | |
{ | |
return $gpuControl.MemoryClockBoostDef | |
} | |
return $gpuControl.MemoryClockDef | |
} | |
Function Set-GPUMemClock($gpuControl, $clock) | |
{ | |
if($flagMemBoost) | |
{ | |
if($clock -lt $gpuControl.MemoryClockBoostMin) | |
{ | |
return "clock_too_low" | |
} | |
elseif($clock -gt $gpuControl.MemoryClockBoostMax) | |
{ | |
return "clock_too_high" | |
} | |
$gpuControl.MemoryClockBoostCur = $clock | |
$abControl.CommitChanges() | |
return | |
} | |
elseif($flagMemClock) | |
{ | |
if($clock -lt $gpuControl.MemoryClockMin) | |
{ | |
return "clock_too_low" | |
} | |
elseif($clock -gt $gpuControl.MemoryClockMax) | |
{ | |
return "clock_too_high" | |
} | |
$gpuControl.MemoryClockCur = $clock | |
$abCOntrol.CommitChanges() | |
return | |
} | |
throw "Failed to set memory clocks: no supported methods available" | |
} | |
# ============================================================================ # | |
Function Read-StreamLine([System.IO.Stream]$reader) | |
{ | |
$line = $null | |
while(($byte = $reader.ReadByte()) -ne -1) | |
{ | |
if([char]$byte -eq [char]"`n") | |
{ | |
if($line -eq $null) | |
{ | |
$line = "" | |
} | |
break | |
} | |
$line += [string][char]$byte | |
} | |
if($line -eq $null) { | |
return $null | |
} | |
return $line.TrimEnd("\r") | |
} | |
Function Run-MemTestInstance($gpuIndex, $memSize, $iterations=10) | |
{ | |
$ret = "success" | |
Write-Progress -Activity "Testing memory" ` | |
-CurrentOperation ("Iteration {0} of {1}" -f 0, $iterations) ` | |
-PercentComplete 0 | |
$partialIterations = [Math]::Min($iterations, 5) | |
$pinfo = New-Object System.Diagnostics.ProcessStartInfo | |
$pinfo.FileName = "$memTestFolder/memtestg80.exe" | |
$pinfo.RedirectStandardOutput = $true | |
$pinfo.UseShellExecute = $false | |
$pinfo.CreateNoWindow = $true | |
$pinfo.Arguments = @("--bancomm","--gpu",$gpuIndex,$memSize,$partialIterations) | |
$p = New-Object System.Diagnostics.Process | |
$p.StartInfo = $pinfo | |
$totalIterations = $iterations | |
for(; $iterations -gt 0; $iterations -= $partialIterations) | |
{ | |
if($iterations -lt $partialIterations) { | |
$pinfo.Arguments = @("--bancomm","--gpu",$gpuIndex,$memSize,$iterations) | |
} | |
[void]$p.Start() | |
while(($line = Read-StreamLine $p.StandardOutput.BaseStream) -ne $null) | |
{ | |
if($line -imatch 'unable to allocate') | |
{ | |
$ret = "nomem" | |
break | |
} | |
elseif($line -imatch 'invalid GPU index') | |
{ | |
$ret = "invalid_gpu" | |
break | |
} | |
elseif($line -imatch "Final error count.*: (\d+) errors") | |
{ | |
$errors = [int]($Matches[1]) | |
if($errors -gt 0) | |
{ | |
$ret = "fail" | |
break | |
} | |
$iterationsDone = ($totalIterations - $iterations) + $partialIterations | |
$percentComplete = [int]($iterationsDone / $totalIterations * 100) | |
Write-Progress -Activity "Testing memory" ` | |
-CurrentOperation ("Iteration {0} of {1}" -f $iterationsDone,$totalIterations) ` | |
-PercentComplete $percentComplete | |
} | |
} | |
[void]$p.StandardOutput.ReadToEnd() | |
[void]$p.WaitForExit() | |
if($ret -ne "success") | |
{ | |
break | |
} | |
} | |
Write-Progress -Completed $true | |
return $ret | |
} | |
Function Prevent-Sleep | |
{ | |
$funcSignature = @' | |
[FlagsAttribute] | |
public enum EXECUTION_STATE : uint | |
{ | |
ES_AWAYMODE_REQUIRED = 0x00000040, | |
ES_CONTINUOUS = 0x80000000, | |
ES_DISPLAY_REQUIRED = 0x00000002, | |
ES_SYSTEM_REQUIRED = 0x00000001 | |
// Legacy flag, should not be used. | |
// ES_USER_PRESENT = 0x00000004 | |
} | |
[DllImport("kernel32.dll", CharSet = CharSet.Auto,SetLastError = true)] | |
public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); | |
'@ | |
try { | |
$type = Add-Type ` | |
-MemberDefinition $funcSignature ` | |
-Name Win32SetThreadExecutionState ` | |
-Namespace Win32Functions ` | |
-Passthru | |
} catch { | |
return $false | |
} | |
$EXECUTION_STATE = $type | ? {$_.Name -eq 'EXECUTION_STATE'} | |
$cls = $type | ? {$_.Name -eq 'Win32SetThreadExecutionState'} | |
$flags = @("ES_CONTINUOUS", "ES_SYSTEM_REQUIRED") -as $EXECUTION_STATE | |
if($flags -eq $null) | |
{ | |
return $false | |
} | |
$result = $cls::SetThreadExecutionState($flags) | |
return ($result -ne 0) | |
} | |
# ============================================================================ # | |
Function Write-Success($str="") | |
{ | |
Write-Host ("SUCCESS " + $str) -ForegroundColor Green | |
} | |
Function Write-Fail($str="") | |
{ | |
Write-Host ("FAIL " + $str) -ForegroundColor Red | |
} | |
Function Write-TestInfo($nextClock, $currentMemory, [boolean]$isFinalTest) | |
{ | |
if($isFinalTest) { | |
Write-Host "Testing final clock " -NoNewLine | |
} else { | |
Write-Host "Trying " -NoNewline | |
} | |
Write-Host (Format-Clock $nextClock $flagMemBoost) -ForegroundColor Yellow -NoNewline | |
Write-Host " with " -NoNewline | |
Write-Host ("{0}MB" -f $currentMemory) -ForegroundColor Cyan -NoNewline | |
Write-Host " and " -NoNewline | |
Write-Host $iterations -ForegroundColor Magenta -NoNewLine | |
Write-Host " iterations: " -NoNewline | |
} | |
$currentMemory = $gpuMemory | |
$originalClock = Get-GPUMemClock $gpuControl | |
switch($startClock) | |
{ | |
"current" {$currentClock = $originalClock} | |
"stock" {$currentClock = Get-GPUMemDefClock $gpuControl} | |
default {$currentClock = $startClock} | |
} | |
if($targetClock -gt 0) | |
{ | |
$clockStep = $targetClock - $currentClock | |
} | |
else | |
{ | |
$clockStep = $stepDefault | |
} | |
$isFinalTest = $false | |
$failedTests = @(0) | |
Prevent-Sleep | |
while($true) | |
{ | |
if($isFinalTest) { | |
$nextClock = $currentClock | |
$iterations = $testIterations * 10 | |
} else { | |
$nextClock = [int]($currentClock + $clockStep) | |
$iterations = $testIterations | |
} | |
# Skip the test if the clock is known to be bad | |
if($nextClock -in $failedTests) | |
{ | |
$result = "skip" | |
} | |
else | |
{ | |
Write-TestInfo $nextClock $currentMemory $isFinalTest | |
Set-GPUMemClock $gpuControl $nextClock | |
$result = Run-MemTestInstance $gpuIndex $currentMemory $iterations | |
} | |
if($result -eq "invalid_gpu") | |
{ | |
Write-Fail "(INVALID GPU)" | |
throw 'Invalid GPU index' | |
} | |
elseif($result -eq "clock_too_high") | |
{ | |
$result = "fail" | |
} | |
elseif($result -eq "clock_too_low") | |
{ | |
throw 'Minimum clock reached, cannot proceed' | |
} | |
elseif($result -eq "nomem") | |
{ | |
Write-Fail "(NOT ENOUGH MEMORY)" | |
$currentMemory -= $memoryStep | |
$currentMemory -= $currentMemory % 16 | |
continue | |
} | |
if($result -eq "success") | |
{ | |
$currentClock = $nextClock | |
Write-Success | |
if($isFinalTest) | |
{ | |
break | |
} | |
elseif($clockStep -lt 0) | |
{ | |
$isFinalTest = $true | |
} | |
} | |
else | |
{ | |
if($result -ne "skip") | |
{ | |
# Revert the last increment if a final test failed | |
if($isFinalTest) | |
{ | |
Write-Fail "(FINAL TEST FAILED, REVERTING)" | |
$isFinalTest = $false | |
} | |
else | |
{ | |
Write-Fail | |
} | |
} | |
if($clockStep -gt 0) | |
{ | |
$clockStep *= $stepFactor | |
if($clockStep -lt $stepMin) | |
{ | |
# Enter regression mode, clock speeds will only go down now | |
$clockStep = -($stepMin) | |
$isFinalTest = $true | |
} | |
} | |
else | |
{ | |
# Normally a failure means keeping the current clocks, but since we're | |
# in regression mode we actually need to pick up the lower clocks | |
$currentClock = $nextClock | |
} | |
# Keep track of known bad clocks | |
$failedTests += $nextClock | |
} | |
} | |
Write-Host "Final clock: " -NoNewLine | |
Write-Host (Format-Clock $currentClock $flagMemBoost) -ForegroundColor Yellow | |
Read-Host "Press ENTER to continue ..." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment