Skip to content

VRAM Calculator — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// vram-calculator — Zig port: estimate the VRAM an LLM needs (weights + KV cache).
const std = @import("std");

/// Supported weight quantizations, in the order the UI lists them.
pub const Quant = enum { fp32, fp16, bf16, int8, int4, q4_K_M };

/// Bytes stored per weight for each format
/// (Q4_K_M = 4.85 bits/weight, the llama.cpp mix).
pub fn bytesPerParam(q: Quant) f64 {
    return switch (q) {
        .fp32 => 4,
        .fp16, .bf16 => 2,
        .int8 => 1,
        .int4 => 0.5,
        .q4_K_M => 4.85 / 8,
    };
}

/// Attention architecture and serving knobs; every field must be >= 1. The
/// defaults are a modern GQA-style layout — Llama-2-70B overrides layers to
/// 80 with the same GQA shape.
pub const VramOptions = struct {
    layers: i64 = 32,    // transformer layers (blocks)
    kv_heads: i64 = 8,   // key/value heads after GQA
    head_dim: i64 = 128, // dimension of one attention head
    batch: i64 = 1,      // sequences served concurrently; multiplies the KV cache
    kv_bytes: i64 = 2,   // bytes per KV element (fp16 K and V tensors)
};

/// One VRAM estimate, in decimal gigabytes (GB = 10^9, matching how "70B"
/// and GPU sizes are quoted).
pub const VramBreakdown = struct {
    quant: Quant,
    bytes_per_param: f64,
    weights_gb: f64,
    kv_cache_gb: f64,
    total_gb: f64,
};

pub const VramError = error{ InvalidParams, InvalidOption };

/// Estimate the VRAM footprint of a model: weights plus KV cache.
///
/// weightsGB = paramsB × bytesPerParam
/// kvCacheGB = 2 × layers × context × kvHeads × headDim × kvBytes × batch / 1e9
///
/// Errors on paramsB ≤ 0, negative context, or any option below 1
/// (context 0 is allowed — no context, no cache).
pub fn vram(params_b: f64, q: Quant, context: i64, opts: VramOptions) VramError!VramBreakdown {
    if (!std.math.isFinite(params_b) or params_b <= 0) return error.InvalidParams;
    if (context < 0) return error.InvalidParams;
    if (opts.layers < 1 or opts.kv_heads < 1 or opts.head_dim < 1 or opts.batch < 1 or opts.kv_bytes < 1)
        return error.InvalidOption;
    const bpp = bytesPerParam(q);
    const weights_gb = params_b * 1e9 * bpp / 1e9; // the 1e9s cancel
    // KV elements fit i64 for any realistic shape (70B @ ctx 4096 × batch 2 = 5.4e9).
    const kv_elems: i64 = 2 * opts.layers * context * opts.kv_heads * opts.head_dim * opts.kv_bytes * opts.batch;
    const kv_cache_gb = @as(f64, @floatFromInt(kv_elems)) / 1e9;
    return .{ .quant = q, .bytes_per_param = bpp, .weights_gb = weights_gb, .kv_cache_gb = kv_cache_gb, .total_gb = weights_gb + kv_cache_gb };
}

/// A GPU memory tier, and one card scored against a total.
pub const GpuCard = struct { name: []const u8, size_gb: f64 };
pub const GpuFit = struct { name: []const u8, size_gb: f64, fits: bool, headroom_gb: f64 };

/// Common GPU memory tiers, from consumer boards to datacenter cards.
pub const gpu_cards = [_]GpuCard{
    .{ .name = "RTX 3060 Ti / RTX 4060 / RX 7600", .size_gb = 8 },
    .{ .name = "RTX 3060 12 GB / RTX 4070", .size_gb = 12 },
    .{ .name = "RTX 4060 Ti 16 GB / RTX 5080", .size_gb = 16 },
    .{ .name = "RTX 3090 / RTX 4090", .size_gb = 24 },
    .{ .name = "RTX A6000 / L40S", .size_gb = 48 },
    .{ .name = "A100 80 GB / H100 / H200", .size_gb = 80 },
};

/// Score every card against a total footprint. `fits` is inclusive: a total
/// exactly equal to the card size fits (headroom 0). `out` must fit `cards`.
pub fn gpuFits(total_gb: f64, cards: []const GpuCard, out: []GpuFit) VramError![]GpuFit {
    if (!std.math.isFinite(total_gb) or total_gb < 0) return error.InvalidParams;
    var n: usize = 0;
    for (cards) |c| {
        out[n] = .{ .name = c.name, .size_gb = c.size_gb, .fits = c.size_gb >= total_gb, .headroom_gb = c.size_gb - total_gb };
        n += 1;
    }
    return out[0..n];
}

pub fn main() !void {
    // The lib's canonical vectors: a 70B Llama-2-shape build (80 layers) and
    // an 8B default-arch build, then the GPU fit table for the 8B total.
    const r70 = try vram(70, .q4_K_M, 4096, .{ .layers = 80 });
    const r8 = try vram(8, .fp16, 8192, .{});
    const stdout = std.io.getStdOut().writer();
    try stdout.print("70B q4_K_M @4096 (80 layers): {d:.2} GB weights + {d:.2} GB KV = {d:.2} GB total\n", .{ r70.weights_gb, r70.kv_cache_gb, r70.total_gb });
    try stdout.print("8B  fp16    @8192:            {d:.2} GB weights + {d:.2} GB KV = {d:.2} GB total\n", .{ r8.weights_gb, r8.kv_cache_gb, r8.total_gb });
    try stdout.print("GPU fit for {d:.2} GB:\n", .{r8.total_gb});
    var fits: [gpu_cards.len]GpuFit = undefined;
    for (try gpuFits(r8.total_gb, &gpu_cards, &fits)) |f| {
        try stdout.print("  {s:<36}{s} ({d:.2} GB {s})\n", .{
            f.name,
            if (f.fits) "fits" else "too small",
            @abs(f.headroom_gb),
            if (f.fits) "headroom" else "short",
        });
    }
}

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 →