|
| 1 | +# gpuaudit |
| 2 | + |
| 3 | +Scan your cloud for GPU waste and get actionable recommendations to cut your spend. |
| 4 | + |
| 5 | +``` |
| 6 | +$ gpuaudit scan --skip-eks |
| 7 | +
|
| 8 | + Found 38 GPU nodes across 47 nodes in gpu-cluster |
| 9 | +
|
| 10 | + gpuaudit — GPU Cost Audit for AWS |
| 11 | + Account: 123456789012 | Regions: us-east-1 | Duration: 4.2s |
| 12 | +
|
| 13 | + ┌──────────────────────────────────────────────────────────┐ |
| 14 | + │ GPU Fleet Summary │ |
| 15 | + ├──────────────────────────────────────────────────────────┤ |
| 16 | + │ Total GPU instances: 38 │ |
| 17 | + │ Total monthly GPU spend: $127450 │ |
| 18 | + │ Estimated monthly waste: $18200 ( 14%) │ |
| 19 | + └──────────────────────────────────────────────────────────┘ |
| 20 | +
|
| 21 | + CRITICAL — 3 instance(s), $15400/mo potential savings |
| 22 | +
|
| 23 | + Instance Type Monthly Signal Recommendation |
| 24 | + ──────────────────────────────────── ────────────────────────── ──────── ──────────────── ────────────────────────────────────────────── |
| 25 | + gpu-cluster/ip-10-15-255-248 g6e.16xlarge (1× L40S) $ 6752 idle Node up 13 days with 0 GPU pods scheduled. |
| 26 | + gpu-cluster/ip-10-22-250-15 g6e.16xlarge (1× L40S) $ 6752 idle Node up 1 days with 0 GPU pods scheduled. |
| 27 | + ... |
| 28 | +``` |
| 29 | + |
| 30 | +## What it scans |
| 31 | + |
| 32 | +- **EC2** — GPU instances (g4dn, g5, g6, g6e, p4d, p4de, p5, inf2, trn1) with CloudWatch metrics |
| 33 | +- **SageMaker** — Endpoints with GPU utilization and invocation metrics |
| 34 | +- **EKS** — Managed GPU node groups via the AWS EKS API |
| 35 | +- **Kubernetes** — GPU nodes and pod allocation via the Kubernetes API (Karpenter, self-managed, any CNI) |
| 36 | + |
| 37 | +## What it detects |
| 38 | + |
| 39 | +- **Idle GPU instances** — running but doing nothing (low CPU + near-zero network for 24+ hours) |
| 40 | +- **Oversized GPU** — multi-GPU instances where utilization suggests a single GPU would suffice |
| 41 | +- **Pricing mismatch** — on-demand instances running 30+ days that should be Reserved Instances |
| 42 | +- **Stale instances** — non-production instances running 90+ days |
| 43 | +- **SageMaker low utilization** — endpoints with <10% GPU utilization |
| 44 | +- **SageMaker oversized** — endpoints using <30% GPU memory on multi-GPU instances |
| 45 | +- **K8s unallocated GPUs** — nodes with GPU capacity but no pods requesting GPUs |
| 46 | + |
| 47 | +## Install |
| 48 | + |
| 49 | +```bash |
| 50 | +go install github.com/gpuaudit/cli/cmd/gpuaudit@latest |
| 51 | +``` |
| 52 | + |
| 53 | +Or build from source: |
| 54 | + |
| 55 | +```bash |
| 56 | +git clone https://github.com/gpuaudit/cli.git |
| 57 | +cd cli |
| 58 | +go build -o gpuaudit ./cmd/gpuaudit |
| 59 | +``` |
| 60 | + |
| 61 | +## Quick start |
| 62 | + |
| 63 | +```bash |
| 64 | +# Uses default AWS credentials (~/.aws/credentials or environment variables) |
| 65 | +gpuaudit scan |
| 66 | + |
| 67 | +# Specific profile and region |
| 68 | +gpuaudit scan --profile production --region us-east-1 |
| 69 | + |
| 70 | +# Kubernetes cluster scan (uses KUBECONFIG or ~/.kube/config) |
| 71 | +gpuaudit scan --skip-eks |
| 72 | + |
| 73 | +# Specific kubeconfig and context |
| 74 | +gpuaudit scan --kubeconfig ~/.kube/config --kube-context gpu-cluster |
| 75 | + |
| 76 | +# JSON output for automation |
| 77 | +gpuaudit scan --format json -o report.json |
| 78 | + |
| 79 | +# Compare two scans to see what changed |
| 80 | +gpuaudit diff old-report.json new-report.json |
| 81 | + |
| 82 | +# Slack Block Kit payload (pipe to webhook) |
| 83 | +gpuaudit scan --format slack -o - | \ |
| 84 | + curl -X POST -H 'Content-Type: application/json' -d @- $SLACK_WEBHOOK |
| 85 | + |
| 86 | +# Skip specific scanners |
| 87 | +gpuaudit scan --skip-metrics # faster, less accurate |
| 88 | +gpuaudit scan --skip-sagemaker |
| 89 | +gpuaudit scan --skip-eks # skip AWS EKS API (use --skip-k8s for Kubernetes API) |
| 90 | +gpuaudit scan --skip-k8s |
| 91 | +``` |
| 92 | + |
| 93 | +## Comparing scans |
| 94 | + |
| 95 | +Save scan results as JSON, then diff them later: |
| 96 | + |
| 97 | +```bash |
| 98 | +gpuaudit scan --format json -o scan-apr-08.json |
| 99 | +# ... time passes, changes happen ... |
| 100 | +gpuaudit scan --format json -o scan-apr-15.json |
| 101 | +gpuaudit diff scan-apr-08.json scan-apr-15.json |
| 102 | +``` |
| 103 | + |
| 104 | +``` |
| 105 | + gpuaudit diff — 2026-04-08 12:00 UTC → 2026-04-15 12:00 UTC |
| 106 | +
|
| 107 | + ┌──────────────────────────────────────────────────────────┐ |
| 108 | + │ Cost Delta │ |
| 109 | + ├──────────────────────────────────────────────────────────┤ |
| 110 | + │ Monthly spend: $142000 → $127450 (-$14550) │ |
| 111 | + │ Estimated waste: $31000 → $18200 (-$12800) │ |
| 112 | + │ Instances: 45 → 38 (-9 removed, +2 added) │ |
| 113 | + └──────────────────────────────────────────────────────────┘ |
| 114 | +
|
| 115 | + REMOVED — 9 instance(s), -$16200/mo |
| 116 | + ... |
| 117 | +``` |
| 118 | + |
| 119 | +Matches instances by ID. Reports added, removed, and changed instances with per-field diffs (instance type, pricing model, cost, state, GPU allocation, waste severity). |
| 120 | + |
| 121 | +## Multi-Account Scanning |
| 122 | + |
| 123 | +Scan multiple AWS accounts in a single invocation using STS AssumeRole. |
| 124 | + |
| 125 | +### Prerequisites |
| 126 | + |
| 127 | +Deploy a read-only IAM role (`gpuaudit-reader`) to each target account. See [Cross-Account Role Setup](#cross-account-role-setup) below. |
| 128 | + |
| 129 | +### Usage |
| 130 | + |
| 131 | +```bash |
| 132 | +# Scan specific accounts |
| 133 | +gpuaudit scan --targets 111111111111,222222222222 --role gpuaudit-reader |
| 134 | + |
| 135 | +# Scan entire AWS Organization |
| 136 | +gpuaudit scan --org --role gpuaudit-reader |
| 137 | + |
| 138 | +# Exclude management account |
| 139 | +gpuaudit scan --org --role gpuaudit-reader --skip-self |
| 140 | + |
| 141 | +# With external ID |
| 142 | +gpuaudit scan --targets 111111111111 --role gpuaudit-reader --external-id my-secret |
| 143 | +``` |
| 144 | + |
| 145 | +### Cross-Account Role Setup |
| 146 | + |
| 147 | +#### Terraform |
| 148 | + |
| 149 | +```hcl |
| 150 | +variable "management_account_id" { |
| 151 | + description = "AWS account ID where gpuaudit runs" |
| 152 | + type = string |
| 153 | +} |
| 154 | +
|
| 155 | +resource "aws_iam_role" "gpuaudit_reader" { |
| 156 | + name = "gpuaudit-reader" |
| 157 | + assume_role_policy = jsonencode({ |
| 158 | + Version = "2012-10-17" |
| 159 | + Statement = [{ |
| 160 | + Effect = "Allow" |
| 161 | + Principal = { AWS = "arn:aws:iam::${var.management_account_id}:root" } |
| 162 | + Action = "sts:AssumeRole" |
| 163 | + }] |
| 164 | + }) |
| 165 | +} |
| 166 | +
|
| 167 | +resource "aws_iam_role_policy" "gpuaudit_reader" { |
| 168 | + name = "gpuaudit-policy" |
| 169 | + role = aws_iam_role.gpuaudit_reader.id |
| 170 | + policy = file("gpuaudit-policy.json") # from: gpuaudit iam-policy > gpuaudit-policy.json |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +Deploy to all accounts using Terraform workspaces or CloudFormation StackSets. |
| 175 | + |
| 176 | +#### CloudFormation StackSet |
| 177 | + |
| 178 | +```yaml |
| 179 | +AWSTemplateFormatVersion: "2010-09-09" |
| 180 | +Parameters: |
| 181 | + ManagementAccountId: |
| 182 | + Type: String |
| 183 | +Resources: |
| 184 | + GpuAuditRole: |
| 185 | + Type: AWS::IAM::Role |
| 186 | + Properties: |
| 187 | + RoleName: gpuaudit-reader |
| 188 | + AssumeRolePolicyDocument: |
| 189 | + Version: "2012-10-17" |
| 190 | + Statement: |
| 191 | + - Effect: Allow |
| 192 | + Principal: |
| 193 | + AWS: !Sub "arn:aws:iam::${ManagementAccountId}:root" |
| 194 | + Action: sts:AssumeRole |
| 195 | + Policies: |
| 196 | + - PolicyName: gpuaudit-policy |
| 197 | + PolicyDocument: |
| 198 | + Version: "2012-10-17" |
| 199 | + Statement: |
| 200 | + - Effect: Allow |
| 201 | + Action: |
| 202 | + - ec2:DescribeInstances |
| 203 | + - ec2:DescribeInstanceTypes |
| 204 | + - ec2:DescribeRegions |
| 205 | + - sagemaker:ListEndpoints |
| 206 | + - sagemaker:DescribeEndpoint |
| 207 | + - sagemaker:DescribeEndpointConfig |
| 208 | + - eks:ListClusters |
| 209 | + - eks:ListNodegroups |
| 210 | + - eks:DescribeNodegroup |
| 211 | + - cloudwatch:GetMetricData |
| 212 | + - cloudwatch:GetMetricStatistics |
| 213 | + - cloudwatch:ListMetrics |
| 214 | + - ce:GetCostAndUsage |
| 215 | + - ce:GetReservationUtilization |
| 216 | + - ce:GetSavingsPlansUtilization |
| 217 | + - pricing:GetProducts |
| 218 | + Resource: "*" |
| 219 | +``` |
| 220 | +
|
| 221 | +## IAM permissions |
| 222 | +
|
| 223 | +gpuaudit is read-only. It never modifies your infrastructure. Generate the minimal IAM policy: |
| 224 | +
|
| 225 | +```bash |
| 226 | +gpuaudit iam-policy |
| 227 | +``` |
| 228 | + |
| 229 | +For Kubernetes scanning, gpuaudit needs `get`/`list` on `nodes` and `pods` cluster-wide. |
| 230 | + |
| 231 | +## GPU pricing reference |
| 232 | + |
| 233 | +```bash |
| 234 | +# List all GPU instance pricing |
| 235 | +gpuaudit pricing |
| 236 | + |
| 237 | +# Filter by GPU model |
| 238 | +gpuaudit pricing --gpu H100 |
| 239 | +gpuaudit pricing --gpu L4 |
| 240 | +``` |
| 241 | + |
| 242 | +## Output formats |
| 243 | + |
| 244 | +| Format | Flag | Use case | |
| 245 | +|---|---|---| |
| 246 | +| Table | `--format table` (default) | Terminal viewing | |
| 247 | +| JSON | `--format json` | Automation, CI/CD, `gpuaudit diff` | |
| 248 | +| Markdown | `--format markdown` | PRs, wikis, docs | |
| 249 | +| Slack | `--format slack` | Slack webhook integration | |
| 250 | + |
| 251 | +## How it works |
| 252 | + |
| 253 | +1. **Discovery** — Scans EC2, SageMaker, EKS node groups, and Kubernetes API across multiple regions for GPU resources |
| 254 | +2. **Metrics** — Collects 7-day CloudWatch metrics: CPU, network I/O for EC2; GPU utilization, GPU memory, invocations for SageMaker |
| 255 | +3. **K8s allocation** — Lists pods requesting `nvidia.com/gpu` resources and maps them to nodes |
| 256 | +4. **Analysis** — Applies 7 waste detection rules with severity levels (critical/warning/info) |
| 257 | +5. **Recommendations** — Generates specific actions (terminate, downsize, switch pricing) with estimated monthly savings |
| 258 | + |
| 259 | +## Project structure |
| 260 | + |
| 261 | +``` |
| 262 | +gpuaudit/ |
| 263 | +├── cmd/gpuaudit/ CLI entry point (cobra) |
| 264 | +├── internal/ |
| 265 | +│ ├── models/ Core data types (GPUInstance, WasteSignal, Recommendation) |
| 266 | +│ ├── pricing/ Bundled GPU pricing database (40+ instance types) |
| 267 | +│ ├── analysis/ Waste detection rules engine (7 rules) |
| 268 | +│ ├── diff/ Scan comparison logic |
| 269 | +│ ├── output/ Formatters (table, JSON, markdown, Slack, diff) |
| 270 | +│ └── providers/ |
| 271 | +│ ├── aws/ EC2, SageMaker, EKS, CloudWatch, Cost Explorer |
| 272 | +│ └── k8s/ Kubernetes API GPU node/pod discovery |
| 273 | +└── LICENSE Apache 2.0 |
| 274 | +``` |
| 275 | + |
| 276 | +## Roadmap |
| 277 | + |
| 278 | +- [ ] DCGM GPU metrics via Kubernetes (actual GPU utilization, not just allocation) |
| 279 | +- [ ] SageMaker training job analysis |
| 280 | +- [ ] Multi-account (AWS Organizations) scanning |
| 281 | +- [ ] GCP + Azure support |
| 282 | +- [ ] GitHub Action for scheduled scans |
| 283 | + |
| 284 | +## License |
| 285 | + |
| 286 | +Apache 2.0 |
0 commit comments