Prompt Caching Strategies

Reduce LLM inference costs by 80% with prefix caching, KV-cache reuse, and semantic deduplication techniques.

Prompt caching strategies are essential when AI inference latency and cost must scale beyond a handful of manual prompt templates. They matter when the same prompt—possibly parameterized—repeats across thousands of user requests, batch jobs, or agent workflows, and when evaluating a prompt’s cost-effectiveness across varied inputs. The core idea is to reduce redundant computation by storing the result of a prompt evaluation (input + prompt + model parameters) and reusing it when the same or similar input is provided again.

When to Cache Prompts

Cache a prompt when it is invoked frequently with overlapping or identical inputs, especially when:

A prompt is a good candidate for caching if the average time to compute it exceeds 100ms and if the same prompt is reused across 100 or more requests.

Cache Keys: How to Identify a Prompt

A prompt cache key is a deterministic identifier for a prompt’s input and configuration. It must be:

The canonical cache key consists of:

A typical cache key is a SHA-256 hash of the concatenation of:

For example:

import hashlib
import json

def make_cache_key(prompt_template, input_params, model_config, engine_version):
    key_string = (
        prompt_template
        + "\n"
        + json.dumps(input_params, sort_keys=True, separators=(",", ":"))
        + "\n"
        + json.dumps(model_config, sort_keys=True, separators=(",", ":"))
        + "\n"
        + engine_version
    )
    return hashlib.sha256(key_string.encode("utf-8")).hexdigest()

The most common failure mode is inconsistent key generation: the same input produces different keys due to:

> Tip: Always sort JSON keys and normalize whitespace in prompt templates. Use json.dumps(..., sort_keys=True, separators=(",", ":")) and text.strip() before hashing.

Cache Miss and Hit: The Core Workflow

A prompt cache operates on a read-first, write-once pattern:

  1. Cache lookup: use the computed cache key to query a key-value store (e.g., Redis, DynamoDB, PostgreSQL).
  2. If hit: return the cached result immediately.
  3. If miss: compute the prompt output, store it under the cache key, and return it.

The core workflow in pseudocode:

def get_cached_prompt_result(prompt_template, input_params, model_config, engine_version):
    cache_key = make_cache_key(prompt_template, input_params, model_config, engine_version)
    cached_result = cache.get(cache_key)
    if cached_result:
        return cached_result  # Cache hit: return immediately

    # Cache miss: compute result
    prompt = render_prompt(prompt_template, input_params)  # e.g., Jinja2 or string.format
    result = call_llm_model(prompt, model_config)  # e.g., OpenAI API call
    cache.set(cache_key, result, ttl=3600)  # Store for 1 hour

    return result

The most common silent failure: cache misses on identical inputs due to floating-point mismatch.

For example, temperature=0.7 vs temperature=0.7000000000000001 results in two different cache keys, even though the model call would produce identical outputs.

> Solution: round floating-point values to a fixed precision (e.g., 3 decimals) before including them in the cache key.

Cache Eviction and TTL: Managing Memory

Caching is only effective when the cache is warm. Eviction strategies determine which entries are removed when space is limited.

The simplest strategy is time-to-live (TTL):


SET prompt:abc123 "{'output': '...', 'tokens': 230}" EX 3600

A common error: TTL is set on insertion, but access time is not updated.

In a high-read scenario, a popular prompt may be accessed every minute, but its TTL is 1 hour. If the cache never updates the access time, the entry may expire even though it’s frequently used.

> Best practice: use TTL with access tracking (e.g., Redis EXPIRE + EXPIREAT), or implement LRU (Least Recently Used) using a timestamped access log.

Another failure: cache entries are not updated on data changes.

When the underlying data (e.g., user profile, code repository) changes, the cached prompt may become stale. The solution is cache invalidation.

Cache Invalidation: Keeping Results Fresh

Cache invalidation ensures that updated data triggers a cache refresh.

The two primary strategies:

1. Invalidate on data change

When a record used in a prompt is updated, all cache keys that depend on it are invalidated.

For example, a prompt that generates a project summary from project.id=123 invalidates all cache keys that include projectId=123.

-- When a project is updated
UPDATE projects SET name = 'New Name', updated_at = NOW() WHERE id = 123;

-- Invalidate all prompts using this project
DELETE FROM cache_invalidation_log WHERE dependent_key = 'project:123';

-- Or send an event to a queue
{
  "event": "project_updated",
  "project_id": 123,
  "timestamp": "2024-03-15T12:34:56Z"
}

A consumer listens to the queue and invalidates all cache keys that depend on project_id.

> Common pitfall: failing to propagate invalidation across multiple levels of dependency.

A project summary prompt may depend on:

Invalidating project.id does not invalidate the prompt if owner_id changes.

> Solution: use dependency graphs. Store a list of dependent keys for each cache entry.

2. Version-based invalidation

Each prompt template and data source has a version number. When a version changes, all cache keys generated from that version are invalidated.

{
  "prompt_template": "summarize_project_v2.jinja",
  "version": 3,
  "input_params": { "projectId": 123 },
  "model_config": { "model": "gpt-4-turbo", "temperature": 0.7 },
  "cache_key": "prompt:summarize_project_v2:3:123:0.7"
}

When summarize_project_v2.jinja version is updated to 4:

This strategy works well for batched or scheduled prompts.

> Edge case: versioned data sources (e.g., user_profiles_v4.csv) must be updated atomically with their versions.

Multi-Level Caching: Layering for Performance

For high-throughput systems, use multi-level caching:

Each level has different trade-offs:

A typical flow:

  1. Local cache hit → return result immediately.
  2. Local cache miss → query distributed cache.
  3. Distributed cache miss → compute result and store in both distributed and local cache.
  4. All levels update access timestamps.
from cachetools import LRUCache

# Local cache: 10,000 entries
local_cache = LRUCache(maxsize=10_000)

def get_prompt_result(prompt_template, input_params, model_config, engine_version):
    cache_key = make_cache_key(prompt_template, input_params, model_config, engine_version)

    # Try local cache first
    if cache_key in local_cache:
        return local_cache[cache_key]

    # Then distributed cache
    response = requests.get(f"https://cache.example.com/{cache_key}")
    if response.status_code == 200:
        result = response.json()
        local_cache[cache_key] = result
        return result

    # Finally, compute and store
    prompt = render_prompt(prompt_template, input_params)
    result = call_llm_model(prompt, model_config)
    local_cache[cache_key] = result

    # Store in distributed cache
    requests.put(
        f"https://cache.example.com/{cache_key}",
        json=result,
        params={"ttl": 3600}
    )

    return result

The most subtle failure: cache warming.

Cold systems start with empty caches. Without proactive warming, the first 1000 requests are cache misses, causing high latency and cost.

> Solution: pre-load the cache with expected or high-frequency inputs.

# Warm cache with common project IDs
for project_id in [1, 2, 3, 100, 101, 102, 1000, 1001]:
    call_llm_model(
        prompt_template="summarize_project_v2.jinja",
        input_params={"projectId": project_id},
        model_config={"model": "gpt-4-turbo", "temperature": 0.7}
    )

Monitoring and Debugging Cache Health

A prompt cache is only as good as its observability.

Key metrics:

Failure modes:

Use the following diagnostic tools:

{
  "trace_id": "t-12345",
  "prompt_template": "generate_api_docs.jinja",
  "input_params": { "service": "user-service", "version": "v2.3" },
  "model_config": { "model": "gpt-4-turbo", "temperature": 0.5 },
  "cache_key": "prompt:generate_api_docs:1:service:user-service:version:v2.3:0.5",
  "cache_hit": true,
  "cache_age_seconds": 1800,
  "computation_time_ms": 120,
  "output_tokens": 1536
}

> Tip: log cache misses with the full input and computed prompt. This reveals mismatches between expected and actual keys.

Summary: Key Principles

Prompt caching is not a one-off optimization. It is a system that must be designed, monitored, and tuned alongside the AI model and data pipelines.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.