Skip to content

Instantly share code, notes, and snippets.

@danielkza
Last active December 11, 2015 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielkza/4618017 to your computer and use it in GitHub Desktop.
Save danielkza/4618017 to your computer and use it in GitHub Desktop.
Automatic Memory Clock tester for NVIDIA cards
<#
.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