Skip to content

VRAM Calculator — Python source

Estimate the VRAM an LLM needs — weights by quantization plus the KV cache for your context and batch — and see which consumer and datacenter GPUs hold it.

This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.

"""VRAM Calculator — estimate the VRAM an LLM needs (weights + KV cache).

Language: Python (3.9+, standard library only)
Source:   CosmoDev polyglot showcase port of the VRAM Calculator tool,
          ported from src/lib/vramCalculator.ts (the canonical TypeScript
          implementation).
Tool page: https://dev.cosmolabs.org/tools/vram-calculator
License:  display source — part of CosmoDev's polyglot tool pages.

Sizes use decimal gigabytes (GB = 10**9 bytes). Two components are
estimated: weights (params x bytes-per-param) and the KV cache
(2 x layers x context x kv_heads x head_dim x batch x bytes per KV
element). Activations and CUDA-context overhead are not modeled — treat
the total as a floor and leave headroom on the card.
"""

from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Dict, List, Optional, Sequence, Tuple

#: Every supported quantization id, in the order the UI lists them.
QUANTS: Tuple[str, ...] = ("fp32", "fp16", "bf16", "int8", "int4", "q4_K_M")

#: Quantization table: bytes stored per weight for each format
#: (q4_K_M = 4.85 bits/weight, the llama.cpp mix).
BYTES_PER_PARAM: Dict[str, float] = {
    "fp32": 4.0,
    "fp16": 2.0,
    "bf16": 2.0,
    "int8": 1.0,
    "int4": 0.5,
    "q4_K_M": 4.85 / 8.0,
}

#: Default attention architecture: a modern GQA-style layout (32 layers,
#: 8 KV heads, 128-dim heads). Override per model.
DEFAULT_LAYERS = 32
DEFAULT_KV_HEADS = 8
DEFAULT_HEAD_DIM = 128

#: Bytes per KV-cache element (fp16 K and V tensors) unless overridden.
DEFAULT_KV_BYTES = 2

#: A decimal gigabyte.
GB = 1e9


def _require_finite_min(name: str, value: float, minimum: float) -> float:
    """Return ``value``; raise ValueError unless it is finite and >= minimum."""
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise ValueError(f"{name} must be a finite number ≥ {minimum} (got {value!r})")
    if not math.isfinite(value) or value < minimum:
        raise ValueError(f"{name} must be a finite number ≥ {minimum} (got {value})")
    return float(value)


@dataclass(frozen=True)
class VramBreakdown:
    """The estimated VRAM footprint of a model."""

    quant: str
    bytes_per_param: float
    weights_gb: float
    kv_cache_gb: float
    total_gb: float


def vram(
    params_b: float,
    quant: str,
    context: float,
    *,
    layers: Optional[int] = None,
    kv_heads: Optional[int] = None,
    head_dim: Optional[int] = None,
    batch: Optional[int] = None,
    kv_bytes: Optional[int] = None,
) -> VramBreakdown:
    """Estimate the VRAM footprint of a model: weights plus KV cache.

    ::

        weights_gb  = params_b * bytes_per_param
        kv_cache_gb = 2 * layers * context * kv_heads * head_dim * kv_bytes * batch / 1e9

    Raises ValueError on params_b <= 0, an unknown quantization, negative
    context, or any option below 1 (context 0 is allowed — no context, no
    cache).
    """
    if isinstance(params_b, bool) or not isinstance(params_b, (int, float)):
        raise ValueError(f"paramsB must be a finite number > 0 (got {params_b!r})")
    if not math.isfinite(params_b) or params_b <= 0:
        raise ValueError(f"paramsB must be a finite number > 0 (got {params_b})")
    try:
        bytes_per_param = BYTES_PER_PARAM[quant]
    except KeyError:
        expected = ", ".join(QUANTS)
        raise ValueError(f"unknown quantization {quant!r} — expected one of {expected}") from None

    _require_finite_min("context", context, 0)
    n_layers = _require_finite_min("layers", DEFAULT_LAYERS if layers is None else layers, 1)
    n_kv_heads = _require_finite_min("kvHeads", DEFAULT_KV_HEADS if kv_heads is None else kv_heads, 1)
    n_head_dim = _require_finite_min("headDim", DEFAULT_HEAD_DIM if head_dim is None else head_dim, 1)
    n_batch = _require_finite_min("batch", 1 if batch is None else batch, 1)
    n_kv_bytes = _require_finite_min("kvBytes", DEFAULT_KV_BYTES if kv_bytes is None else kv_bytes, 1)

    weights_gb = params_b * 1e9 * bytes_per_param / GB
    kv_cache_gb = 2 * n_layers * context * n_kv_heads * n_head_dim * n_kv_bytes * n_batch / GB
    return VramBreakdown(
        quant=quant,
        bytes_per_param=bytes_per_param,
        weights_gb=weights_gb,
        kv_cache_gb=kv_cache_gb,
        total_gb=weights_gb + kv_cache_gb,
    )


@dataclass(frozen=True)
class GpuCard:
    """One GPU memory tier."""

    name: str
    size_gb: float


#: Common GPU memory tiers, from consumer boards to datacenter cards.
GPU_CARDS: Tuple[GpuCard, ...] = (
    GpuCard("RTX 3060 Ti / RTX 4060 / RX 7600", 8),
    GpuCard("RTX 3060 12 GB / RTX 4070", 12),
    GpuCard("RTX 4060 Ti 16 GB / RTX 5080", 16),
    GpuCard("RTX 3090 / RTX 4090", 24),
    GpuCard("RTX A6000 / L40S", 48),
    GpuCard("A100 80 GB / H100 / H200", 80),
)


@dataclass(frozen=True)
class GpuFit:
    """One card scored against a total footprint."""

    name: str
    size_gb: float
    fits: bool
    headroom_gb: float


def gpu_fits(
    total_gb: float,
    cards: Optional[Sequence[GpuCard]] = None,
) -> List[GpuFit]:
    """Score every card against a total footprint.

    ``fits`` is inclusive: a total exactly equal to the card size fits
    (headroom 0). Pass a custom ``cards`` list to score other tiers.
    """
    _require_finite_min("totalGB", total_gb, 0)
    tiers = GPU_CARDS if cards is None else tuple(cards)
    return [
        GpuFit(
            name=c.name,
            size_gb=c.size_gb,
            fits=(c.size_gb - total_gb) >= 0,
            headroom_gb=c.size_gb - total_gb,
        )
        for c in tiers
    ]

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →