Skip to content

Cache Savings Calculator — PHP source

See what prompt caching saves — uncached vs cached cost over N requests, with the write-premium break-even point.

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

<?php
/**
 * cache_savings — uncached vs prompt-cached LLM cost comparison.
 *
 * Language: PHP (8.1+, standard library only)
 * Source:   CosmoDev polyglot showcase port of the Cache Savings Calculator
 *           tool, ported from src/lib/cacheSavings.ts (the canonical
 *           TypeScript implementation).
 * Tool:     https://dev.cosmolabs.org/tools/cache-savings-calculator
 * License:  display source — part of CosmoDev's polyglot tool pages.
 *
 * Design goals:
 *   - Pure + deterministic; never throws.
 *   - Functionally equivalent to the TS reference: same inputs -> same outputs.
 *   - Self-contained: stdlib only (no Composer packages).
 *
 * The TS original takes a full AiModel record but reads only its four pricing
 * rates, so this port narrows the parameter to exactly those fields. Array
 * keys keep the TS camelCase names (the established snippet convention):
 *   $model = ['inputPerM' => 10.0, 'outputPerM' => 50.0,
 *             'cacheReadPerM' => 1.0, 'cacheWritePerM' => 12.5]  // ?float each
 *   $input = ['promptTokens' => 10000, 'outputTokens' => 1000, 'hits' => 5]
 * Any null rate makes every output null — the caller renders an explanatory
 * empty state instead of partial math. All rates are per-1M-token USD,
 * mirroring the cost conventions of llmCost.ts.
 *
 * Reference vectors (fixture model 10 / 50 / 1 / 12.5 — see
 * cacheSavings.test.ts, the lock-step contract every port mirrors):
 *   10k in / 1k out / 5 hits -> uncached 0.75, cached 0.425, savings 0.325,
 *   43.333...% saved, break-even 13 hits. At 1 hit caching LOSES 0.035 (an
 *   honest negative saving). hits < 1 counts as 1. Zero tokens -> zero costs
 *   with 0%. cacheReadPerM 0 -> breakEvenHits null (write premium never repaid).
 */

declare(strict_types=1);

/**
 * The all-null result used when any pricing rate is missing. Exposed as a
 * function (not a constant) so every caller gets a fresh array.
 */
function cache_math_nulled(): array
{
    return [
        'uncached'      => null,
        'cached'        => null,
        'savings'       => null,
        'savingsPct'    => null,
        'breakEvenHits' => null,
    ];
}

/**
 * Compare uncached vs prompt-cached cost for one model.
 *
 * @param array{inputPerM: ?float, outputPerM: ?float, cacheReadPerM: ?float, cacheWritePerM: ?float} $model
 *        The four per-1M-token USD pricing rates cacheMath reads from the TS AiModel.
 * @param array{promptTokens: float, outputTokens: float, hits: float} $input
 *        Prompt/output tokens per request and the cache-hit count (values < 1 are treated as 1).
 *
 * @return array{uncached: ?float, cached: ?float, savings: ?float, savingsPct: ?float, breakEvenHits: ?int}
 *         uncached:      hits × (prompt·in$/M + output·out$/M) / 1e6.
 *         cached:        (prompt·write$/M + hits × (prompt·read$/M + output·out$/M)) / 1e6 —
 *                        one cache write, `hits` cache reads, output billed every request.
 *         savings:       uncached − cached (negative when caching costs more).
 *         savingsPct:    savings / uncached × 100; 0 when uncached is 0.
 *         breakEvenHits: ceil(write$/M / read$/M) when read$/M > 0 — cache hits needed
 *                        for cumulative READ spend to equal ONE write premium; null otherwise.
 */
function cache_math(array $model, array $input): array
{
    $ipM = $model['inputPerM'];
    $opM = $model['outputPerM'];
    $cr  = $model['cacheReadPerM'];
    $cw  = $model['cacheWritePerM'];
    if ($ipM === null || $opM === null || $cr === null || $cw === null) {
        return cache_math_nulled();
    }

    $hits = max(1, $input['hits']);
    $inT  = $input['promptTokens'];
    $outT = $input['outputTokens'];

    // One cache write, `hits` cache reads; output tokens are billed on every request.
    $uncached = ($hits * ($inT * $ipM + $outT * $opM)) / 1000000;
    $cached   = ($inT * $cw + $hits * ($inT * $cr + $outT * $opM)) / 1000000;
    $savings  = $uncached - $cached;
    // Loose comparison on purpose: PHP int(0) must equal float(0.0) exactly as
    // TS's `uncached === 0` does across its single number type.
    $savingsPct    = $uncached == 0.0 ? 0 : ($savings / $uncached) * 100;
    $breakEvenHits = $cr > 0 ? (int) ceil($cw / $cr) : null;

    return [
        'uncached'      => $uncached,
        'cached'        => $cached,
        'savings'       => $savings,
        'savingsPct'    => $savingsPct,
        'breakEvenHits' => $breakEvenHits,
    ];
}

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 →