# GPU Fit benchmark for Windows: measures how fast a model runs in your local Ollama and, if you # say yes, shares the result at https://gpufit.dev so others can see real speeds for their hardware. # # irm https://gpufit.dev/bench.ps1 | iex # # Sends: GPU name, GPU memory, GPU count, OS name, Ollama version, model tag, quantization, how much # of the model was on the GPU, context length and measured speeds. Nothing else: no hostname, # username or files. Set $env:GPUFIT_MODEL to skip the model picker. & { $ErrorActionPreference = 'Stop' $Site = if ($env:GPUFIT_SITE) { $env:GPUFIT_SITE } else { 'https://gpufit.dev' } $OllamaHost = if ($env:OLLAMA_HOST) { $env:OLLAMA_HOST } else { 'http://127.0.0.1:11434' } if ($OllamaHost -notmatch '^https?://') { $OllamaHost = "http://$OllamaHost" } $OllamaHost = $OllamaHost.TrimEnd('/') -replace '0\.0\.0\.0', '127.0.0.1' $Runs = 3; $Tokens = 256; $Ctx = 4096 function Post($path, $body) { Invoke-RestMethod -Method Post -Uri "$OllamaHost$path" -ContentType 'application/json' -Body ($body | ConvertTo-Json -Depth 5 -Compress) } function Median($xs) { $s = @($xs | Sort-Object); if (-not $s.Count) { return $null }; $m = [math]::Floor($s.Count / 2); if ($s.Count % 2) { $s[$m] } else { ($s[$m - 1] + $s[$m]) / 2 } } try { $version = (Invoke-RestMethod "$OllamaHost/api/version").version } catch { Write-Host "Ollama isn't answering at $OllamaHost. Open the Ollama app and try again." -ForegroundColor Red; return } $model = $env:GPUFIT_MODEL if (-not $model) { $list = @((Invoke-RestMethod "$OllamaHost/api/tags").models | ForEach-Object { $_.name } | Where-Object { $_ -notmatch 'embed' }) if (-not $list.Count) { Write-Host 'You have no models in Ollama yet. Try: ollama pull qwen3:8b' -ForegroundColor Red; return } Write-Host 'Your Ollama models:' for ($i = 0; $i -lt $list.Count; $i++) { Write-Host (" {0}) {1}" -f ($i + 1), $list[$i]) } $pick = Read-Host 'Benchmark which one? [1]' if (-not $pick) { $pick = '1' } $model = if ($pick -match '^\d+$') { $list[[int]$pick - 1] } else { $pick } if (-not $model) { Write-Host 'No model picked.' -ForegroundColor Red; return } } try { $show = Post '/api/show' @{ model = $model } } catch { Write-Host "Ollama doesn't have '$model'. Pull it first: ollama pull $model" -ForegroundColor Red; return } $quant = if ($show.details.quantization_level) { $show.details.quantization_level } else { 'unknown' } # GPU: nvidia-smi gives exact memory; otherwise the display adapter name (AMD/Intel). $gpu = 'unknown'; $mem = $null; $count = 1 $smi = Get-Command nvidia-smi -ErrorAction SilentlyContinue if ($smi) { $lines = @(& $smi.Source --query-gpu=name,memory.total --format=csv,noheader,nounits | Where-Object { $_ }) if ($lines.Count) { $count = $lines.Count; $p = $lines[0].Split(','); $gpu = $p[0].Trim(); $mem = [math]::Round([double]$p[1] / 1024, 1) } } else { $vc = Get-CimInstance Win32_VideoController | Where-Object { $_.Name -notmatch 'Microsoft|Intel|Virtual|Parsec' } | Select-Object -First 1 if ($vc) { $gpu = $vc.Name } # AdapterRAM is capped at 4 GB, so read the real size from the driver registry key when it's there. try { $key = Get-ChildItem 'HKLM:\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}' -ErrorAction Stop | Where-Object { (Get-ItemProperty $_.PSPath).DriverDesc -eq $gpu } | Select-Object -First 1 $q = (Get-ItemProperty $key.PSPath).'HardwareInformation.qwMemorySize' if ($q) { $mem = [math]::Round([double]$q / 1GB, 1) } } catch {} } $gpu = ($gpu -replace '[\"\\]', '').Trim() $prompt = 'Write a detailed, practical guide to growing tomatoes on a balcony. Cover choosing a variety, containers, soil, watering, feeding, pruning, pests and harvesting.' $gen = { param($n) Post '/api/generate' @{ model = $model; prompt = $prompt; stream = $false; options = @{ num_predict = $n; num_ctx = $Ctx; temperature = 0; seed = 42 } } } Write-Host "Loading $model ($quant)..." $null = & $gen 1 $share = $null try { $ps = (Invoke-RestMethod "$OllamaHost/api/ps").models | Where-Object { $_.name -eq $model -or $_.name -eq "$($model):latest" } | Select-Object -First 1 if ($ps -and $ps.size -gt 0) { $share = [math]::Round($ps.size_vram / $ps.size, 3) } } catch {} $gens = @(); $pps = @() for ($i = 1; $i -le $Runs; $i++) { Write-Host "Run $i of ${Runs}: generating $Tokens tokens..." $r = & $gen $Tokens if ($r.eval_duration -gt 0) { $gens += [math]::Round($r.eval_count / ($r.eval_duration / 1e9), 2) } if ($r.prompt_eval_duration -gt 0 -and $r.prompt_eval_count -gt 8) { $pps += [math]::Round($r.prompt_eval_count / ($r.prompt_eval_duration / 1e9), 1) } } $genTps = Median $gens; $promptTps = Median $pps if (-not $genTps) { Write-Host "Ollama didn't report timings; update Ollama and try again." -ForegroundColor Red; return } Write-Host '' Write-Host "$model ($quant) on ${gpu}: $genTps tokens/s generating$(if ($promptTps) { ", $promptTps tokens/s reading the prompt" })." -ForegroundColor Green if ($share -ne $null -and $share -lt 0.99) { Write-Host "Note: only $([int]($share * 100))% of the model is on the GPU, so part of it runs from system RAM." } $body = [ordered]@{ gpu = $gpu; mem_gb = $mem; gpu_count = $count; model = $model; quant = $quant; gen_tps = $genTps; prompt_tps = $promptTps; ctx = $Ctx; gpu_share = $share; runtime = 'ollama'; runtime_version = $version; os = 'windows'; client = 'bench.ps1/1' } | ConvertTo-Json -Compress Write-Host '' Write-Host "This is everything that would be sent to ${Site}:" Write-Host " $body" $ok = Read-Host 'Share it so others can see real speeds for this hardware? [Y/n]' if ($ok -match '^[nN]') { Write-Host 'Not sent.'; return } try { $res = Invoke-RestMethod -Method Post -Uri "$Site/api/bench" -ContentType 'application/json' -Body $body Write-Host $res.message -ForegroundColor Green } catch { $msg = $_.ErrorDetails.Message; try { $msg = ($msg | ConvertFrom-Json).error } catch {} Write-Host "Not accepted: $msg" -ForegroundColor Red } }