DEV Community

Cover image for How to Optimize GPU Memory Usage to Cut Costs
nishaant dixit
nishaant dixit

Posted on Originally published at sivaro.in

How to Optimize GPU Memory Usage to Cut Costs

This article was originally published at sivaro.in

How to Optimize GPU Memory Usage to Cut Costs

Your GPU bill is not a math problem. It's a memory problem wearing a math costume.

I learned this the expensive way. In early 2024, we had a client—a fintech company in Bangalore running a real-time fraud scoring model—burning through $41,000 a month on A100 rentals across two clouds. Their data science lead kept telling me they needed more compute. Newer cards. Bigger clusters. I pulled the utilization logs and almost laughed. Their GPUs were sitting at 31% memory utilization on average. They were paying for memory they never touched.

We rewrote their batching and inference pipeline over three weeks. Same model, same latency targets, same throughput. Their bill dropped to $14,200/month. Nothing about the hardware changed.

That's the entire game. If you're asking how to optimize GPU memory usage to cut costs, you don't start by shopping for a cheaper GPU. You start by understanding what's actually eating your VRAM—because the thing you think is eating it usually isn't.

This guide is a buying guide in the broadest sense. It compares optimization techniques the way you'd compare vendors: what each one does, what it costs you in engineering time, where it breaks, and whether it's worth it for your workload. By the end you'll know which levers to pull first for your specific situation.

Why GPU Memory Is Your Real Cost Driver

Most people think GPU cost scales with compute. They're wrong. It scales with memory.

Here's why. On cloud providers, the price gap between GPU tiers tracks memory capacity and bandwidth far more than raw FLOPS. An A100 80GB costs roughly 2.2x an A100 40GB on the major clouds, but it delivers the same compute. You're paying for VRAM. On the new Blackwell generation—B200s started hitting general availability in volume earlier this year—the 192GB HBM3e variant commands a premium that has almost nothing to do with matrix throughput.

And that matters because memory determines batch size, and batch size determines whether you saturate the compute you paid for. Underutilized GPU memory is the single most common reason organizations overprovision. They can't fit a bigger batch, so they spin up a second instance. Now they're paying twice for half the efficiency.

There's a second-order effect too. When a model doesn't fit in memory, you spill to host RAM or NVMe. Now your latency balloons and your throughput tanks, so the only fix your team can think of is—you guessed it—more GPUs. The tail wags the dog.

I've walked into this exact situation at three different companies. Not one of them had a compute problem. All three had a memory accounting problem. So let's fix the accounting.

The Memory Budget Audit You Should Run This Week

Before you optimize anything, measure. Most teams optimize blind and waste a month.

Here's the budget for a single training step or inference pass:

import torch

def memory_budget(model, batch_size, seq_len, dtype_bytes=2):
    # Weights
    param_bytes = sum(p.numel() * dtype_bytes for p in model.parameters())

    # Gradients (training only, same size as params)
    grad_bytes = param_bytes

    # Optimizer state (Adam = 2x params in fp32)
    optim_bytes = sum(p.numel() * 4 * 2 for p in model.parameters())

    # Activations — roughly batch * seq * hidden * layers * bytes
    # This is the wildcard. Profile it, don't guess it.
    activation_bytes = None  # measure with torch.cuda.memory_allocated()

    total = param_bytes + grad_bytes + optim_bytes
    return {
        "weights_gb": param_bytes / 1e9,
        "grads_gb": grad_bytes / 1e9,
        "optimizer_gb": optim_bytes / 1e9,
        "subtotal_gb": total / 1e9,
    }

for k, v in memory_budget(model, 32, 2048).items():
    print(f"{k}: {v:.2f}")
Enter fullscreen mode Exit fullscreen mode

Run this on your actual model. Nine times out of ten, the optimizer state is the surprise—Adam holds two fp32 copies per parameter, so a 7B model in bf16 carries roughly 84GB of optimizer state alone. That's why you can't fit training on a single 80GB card even though the weights are only 14GB.

The activation number you can't compute—you have to measure it. Use torch.cuda.max_memory_allocated() and vary your batch size until you find the wall. That wall is your real constraint.

Technique Comparison: What Actually Moves the Needle

I'll rank these by ROI for most teams, based on what we've deployed at SIVARO across client workloads. Your mileage varies with model size and latency tolerance.

Quantization: The Biggest Single Lever

If you do nothing else, do this. Moving from fp16 to int8 cuts weight memory in half. int4 cuts it to a quarter. For inference especially, the quality loss on well-calibrated quantization is now negligible—we routinely see under 1% accuracy degradation on int8 for production models.

The catch: training-time quantization is still fussy, and not every layer quantizes gracefully. Attention projections and LayerNorm are the usual troublemakers.

from transformers import AutoModelForCausalLM, BitsAndBytesConfig
import torch

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_quant_type="nf4",      # normal float 4 — better than fp4
    bnb_4bit_use_double_quant=True,  # quantize the quantization constants
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B",
    quantization_config=bnb_config,
    device_map="auto",
)
Enter fullscreen mode Exit fullscreen mode

That snippet turns an 8B model from ~16GB in fp16 to roughly 5GB. You just bought back 11GB of VRAM per replica.

Paged Attention and KV Cache Management

The KV cache is where inference memory goes to die. For long-context workloads—and everyone's shipping them now—KV cache can dwarf model weights. A 70B model at 128K context can hold more cache than weights.

vLLM's PagedAttention treats KV cache like virtual memory pages, eliminating fragmentation. In our benchmarks, it typically lets us run 2-4x more concurrent requests in the same VRAM versus naive allocation.

from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    gpu_memory_utilization=0.90,   # leave 10% headroom for CUDA overhead
    max_model_len=32768,
    enable_prefix_caching=True,    # huge win for shared system prompts
    quantization="awq",
)

outputs = llm.generate(prompts, SamplingParams(temperature=0.7, max_tokens=512))
Enter fullscreen mode Exit fullscreen mode

The enable_prefix_caching flag is free money if your users share system prompts. We've seen 40% cache-hit rates on customer support workloads, which translates directly to throughput.

Gradient Checkpointing and Activation Offload

This one trades compute for memory. You recompute activations during the backward pass instead of storing them. Roughly 20-30% slower training for a 50-70% activation memory reduction.

from torch.utils.checkpoint import checkpoint

class CheckpointedBlock(torch.nn.Module):
    def __init__(self, layer):
        super().__init__()
        self.layer = layer

    def forward(self, x):
        return checkpoint(self.layer, x, use_reentrant=False)
Enter fullscreen mode Exit fullscreen mode

Wrap every transformer block and your activation memory collapses. It's not free—recomputation costs you wall-clock time—but if you're memory-bound on a single node, it's cheaper than renting a second one. Usually.

Sharding and Distributed Strategies

FSDP, DeepSpeed ZeRO, and tensor parallelism split model state across GPUs. This doesn't reduce total memory across the cluster; it reduces per-GPU memory. That's still valuable: it lets you fit a model on smaller, cheaper GPUs instead of renting the biggest card available.

Strategy Splits Best For Overhead
DDP Nothing (replicates) Small models, throughput Low
FSDP Params, grads, optim Large models, single-node-ish Medium
ZeRO-3 Params, grads, optim Very large models High
Tensor Parallel Individual layers Latency-sensitive inference Medium
Pipeline Parallel Layer groups Huge models across nodes High (bubbles)

Here's the contrarian part. Most teams reach for ZeRO-3 reflexively because it's the "most aggressive." Don't. The communication overhead on typical interconnects eats the savings, and you end up slower on the same hardware. FSDP is the sweet spot for most single-node and small-cluster training today. We default to it unless there's a specific reason not to.

GPU Cost Optimization Techniques 2026: What Changed This Year

The GPU cost optimization techniques 2026 conversation looks different from 2024, and if you're still running the old playbook you're leaving money on the table.

First, spot and interruptible instances matured. On AWS and GCP, the reliability of preemptible GPU capacity improved enough this year that checkpointed training workloads can run almost entirely on spot. We now run roughly 70% of our clients' training on spot with periodic checkpointing, reserving on-demand only for the tail. That alone is a 40-60% compute cost cut.

Second, HBM3e availability changed the tier math. 192GB cards mean you can sometimes collapse a two-GPU configuration into one. Fewer GPUs is fewer failure points and lower per-hour cost, even at a higher per-card rate. Do the total-cost math, not the per-card math.

Third, the open-weight model ecosystem caught up enough that fine-tuning a 7B or 13B model on quantized weights frequently beats renting a giant card to run a 70B. The best GPU is often the one you don't rent. I've watched three clients this year replace expensive large-model inference with a fine-tuned smaller model and cut inference costs by 80% with better task accuracy.

That last one isn't a memory trick. It's a memory consequence. Smaller models need less VRAM, so they run on cheaper hardware.

Inference vs Training: Different Games, Different Rules

Don't apply training optimizations to inference. They fight each other.

For inference, memory is dominated by weights plus KV cache. Your levers are quantization, PagedAttention, prefix caching, and continuous batching. Gradient checkpointing is irrelevant. Optimizer state doesn't exist. You can often run at 90%+ GPU memory utilization because there's no backward pass to spike usage.

For training, memory is weights plus gradients plus optimizer state plus activations. Your levers are quantization-aware training, gradient checkpointing, FSDP, and offloading. You want headroom for activation spikes, so 80-85% utilization is the practical ceiling.

The mistake I see repeatedly: teams apply inference-grade quantization to training and wonder why convergence stalls. Post-training quantization (PTQ) is for inference. For training you need QAT (quantization-aware training), which simulates the rounding during the forward pass so gradients stay sane. Different technique, different tooling.

Trade-Offs Nobody Warns You About

Quantization reduces memory but can hurt accuracy on out-of-distribution inputs. Your eval set won't catch it. Your production traffic will, usually at 3am.

PagedAttention helps throughput but adds a scheduling layer that's harder to debug when things go wrong. You're trading operational simplicity for efficiency.

Gradient checkpointing saves memory but lengthens training. If your training job is already long, the wall-clock cost may exceed the money you save on a smaller instance.

Sharding lets you fit bigger models but multiplies your failure surface. More GPUs means more things that break, more communication that can stall, more configs that can drift.

None of these are free. Every optimization is a trade. The job is picking the trades that fit your actual constraints—latency budget, accuracy floor, team's operational maturity, and how much of your bill is training versus inference. Most teams don't know that last number, which is itself a problem.

A Decision Framework You Can Use Today

Start here. Answer these four questions:

What's my memory utilization at steady state? If it's under 60%, you have an easy win—fix batching, add prefix caching, or right-size your instance before optimizing anything.

Is my bottleneck weights, activations, or KV cache? Measure. Don't guess. Each has a different fix.

What's my latency SLA? If it's loose, you can afford recomputation and offloading. If it's tight, quantization and better batching are your friends.

What's my engineering budget? Some optimizations cost a day. Some cost a quarter. Match the technique to the time you have.

Teams that answer these honestly usually cut GPU spend 30-50% without touching hardware. Teams that skip the questions buy bigger GPUs and call it scaling.

FAQ

How much can I actually save by optimizing GPU memory usage?

In our client work, 30-50% is typical, and 60%+ is achievable on inference-heavy workloads where quantization and batching compound. The fintech example from the top of this article cut 65%.

Is quantization safe for production accuracy?

For int8 on well-calibrated models, yes—we regularly see under 1% degradation. int4 is riskier and needs per-task evaluation. Always run your real eval set on the quantized model, not a benchmark.

Does PagedAttention work with any model?

It works with transformer-based models that use KV caching. vLLM supports most popular architectures. Custom architectures may need porting work.

Should I use FSDP or ZeRO-3?

FSDP for most single-node and small-cluster cases. ZeRO-3 only when you genuinely exceed what FSDP can shard, because the communication overhead is real. We default to FSDP.

Can I run training and inference on the same GPU?

You can, but you shouldn't at scale. The memory profiles fight each other. Time-slice your instances instead if you must co-locate.

What's the cheapest way to serve a large model?

Quantize aggressively, serve with vLLM or TGI, enable prefix caching, and run on spot capacity where your SLA allows. We've served 70B-class models for under $0.80 per million tokens this way.

When should I just buy my own GPUs instead of renting?

When your utilization exceeds roughly 60% sustained for a year or more, and your team can operate the hardware. Below that, cloud wins on flexibility. The break-even moved this year because of spot reliability improvements.

Does batching always help?

Up to a point. Past a certain batch size you hit latency SLA violations and memory spikes. There's an optimal batch size and it's workload-specific. Find it empirically.

Key Takeaways

Quantization is your highest-ROI lever—do it before anything else. Measure your memory budget before optimizing, because guessing wastes weeks. Inference and training require different techniques, so don't mix them. FSDP beats ZeRO-3 for most real deployments. And the cheapest GPU is often the one you don't rent at all—sometimes a smaller fine-tuned model beats a giant one on both cost and quality.

The companies winning on GPU economics in 2026 aren't the ones with the biggest clusters. They're the ones who know exactly where every gigabyte of VRAM goes, and who optimize against a measured budget instead of a hunch. That's the entire discipline of how to optimize gpu memory usage to cut costs—you can't cut what you don't measure, and you can't optimize what you haven't budgeted. Start with the audit. The savings follow.

Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Top comments (0)