A vendor-neutral, Kubernetes-native cache-policy control plane for LLM inference.
inference-cache makes routing cache-aware: it tracks which replica already holds a
prompt's prefix warm and returns that as a routing hint, so a gateway can reuse
KV/prefill instead of recomputing it — cutting time-to-first-token and cost. It
orchestrates existing KV-cache technology (LMCache, Mooncake); it is not a new
distributed cache and not the data-plane gateway. Guiding principle — "we decide
routing; the gateway follows": all routing intelligence lives in the server, and the
gateway simply tokenizes → calls LookupRoute → routes to the returned replica →
round-robins on NO_HINT.
One request, end to end: an engine serves a prompt and emits KV-cache events → a
subscriber sidecar reports them to the server → the in-memory index records
(tenant, model, hash_scheme, prefix_hash) → replica → a later LookupRoute returns the
ranked replicas holding that prefix warm. The plane moves metadata about where the KV
lives — never KV tensors or prompt text. See docs/concepts/ for depth.
The API separates three choices: spec.runtime selects the inference runtime,
spec.type selects its engine-local cache integration, and
spec.remoteStorage optionally selects a managed or externally owned remote
provider. Supporting another combination is an adapter addition; the core gRPC
contract stays stable.
- vLLM + LMCache supports host-only caching, a managed or external
LMCacheServer, and managed or externalMooncakestorage. - SGLang + LMCache supports host-only caching or a managed/external Redis remote store.
- SGLang + SGLangHiCache uses SGLang's native host tier and does not accept a remote-storage binding.
See config/samples/ for canonical manifests and
docs/design/cachebackend-api.md for the
compatibility rules retained for older v1alpha1 resources.
Inference Cache helps LLM serving systems reuse work that has already been computed. It observes which engine replicas hold reusable prompt-prefix KV blocks, maintains a cluster-wide view of that cache locality, and returns ranked routing hints so a gateway can send each request to a replica likely to have the best prefix match.
It also gives operators a Kubernetes-native way to provision shared cache backends, connect them to inference engines, and define cache, tenant, and prompt policies through CRDs. Its cache index stores metadata rather than prompt text or KV tensors, and the cache plane never makes the final routing decision. When no useful hint is available, the gateway follows its normal routing policy: Inference Cache is designed to fail open.
- Cache-aware routing hints — ranks engine replicas by reusable prefix locality, including longest-prefix block-chain matches and affinity fallback for requests without an exact match.
- Event-driven cache index — consumes KV-cache events from inference engines and maintains a metadata-only, cluster-wide view of cached prefixes, replicas, and tenants.
- Pluggable engine and backend integration — adapter-based wiring connects supported inference engines to managed or external cache backends without coupling the core API to one implementation.
- Declarative cache policy — Kubernetes CRDs configure backend lifecycle, lookup behavior, tenant isolation and quotas, and expose observed cache state through familiar status and condition surfaces.
- Fail-open request path — returns advisory routing results that gateways may ignore; missing, stale, timed-out, or unusable cache information never prevents the gateway from following its default routing policy.
- Built-in operations surface — provides health and readiness endpoints,
Prometheus metrics and alerts, Kubernetes events, functional probes, and the
inferencecache doctordiagnostic command.
One operator, split across two control-plane binaries (controller + server) plus the operator CLI and the CRDs.
CRDs — the API
api/v1alpha1/— Go types for the six v1alpha1 CRDs (CacheBackend,CachePolicy,CacheTenant,CacheIndex,PromptTemplate,PDTopology) + generated deepcopyconfig/— generated CRD, RBAC, and sample manifests
inferencecache-controller (cmd/controller) — watches CRDs and provisions cache backends
cmd/controller/— controller-runtime manager entrypointinternal/controller/— reconcilerspkg/adapters/runtime/—KVCacheRuntimeAdapters render the cache-server pod/service and inject engine/router pod config; the reconciler drives them through the in-packageRegistry
inferencecache-server (cmd/server) — gRPC policy server + cache-state index + metrics
cmd/server/— gRPC + HTTP server entrypointpkg/server/— gRPC service (LookupRoute,RenderTemplate, …), health, metricsproto/(+ generated stubs) — the gRPC contractpkg/index/— cache-state aggregator (CacheIndex)pkg/render/— mutable-slot prompt rendering engine (the wedge); importable librarypkg/adapters/engine/— engine KV-event hook (feeds the index)
inferencecache (cmd/inferencecache) — operator CLI; doctor runs a read-only pre-flight diagnostic
cmd/inferencecache/— cobra entrypointpkg/cli/doctor/— diagnostic checks + output formatters (seedocs/cli/doctor.md)
Shared — pkg/version/, hack/, dockerfiles/, .githooks/
make proto-gen
make build
make testRun the server locally:
bin/server \
--grpc-bind-address=:9090 \
--http-bind-address=:8080 \
--snapshot-bind-address=:8081 \
--insecure-disable-auth # local-dev only; production passes --allowed-controller-sa instead
curl -i http://localhost:8080/healthz # liveness
curl -i http://localhost:8080/readyz # readiness
curl -s http://localhost:8080/metrics # Prometheus metrics (inferencecache_*)
curl -s http://localhost:8081/snapshot # internal aggregate (auth-gated in production)
curl -i -XPOST http://localhost:8081/policy -d '{"version":3,"policies":[]}' # controller push (auth-gated in production)
curl -i -XPOST http://localhost:8081/probe \
-H 'Content-Type: application/json' \
-d '{"backend":"local/dev","model":"dev-model","hashScheme":"vllm"}' # functional self-test (auth-gated in production)The server fails closed by default: omitting both --allowed-controller-sa and
--insecure-disable-auth causes it to exit 2 with a stderr message. That keeps
an operator who forgets the flag from accidentally shipping unauthenticated
/snapshot, /policy, and /probe endpoints on a real cluster.
The controller serves admission webhooks — defaulting + validation for
CacheBackend, plus a mutating Pod webhook that auto-injects the cache
engine configuration (chosen by the matched runtime adapter) into pods
labeled to match a CacheBackend's spec.engineSelector — over TLS, so
deploying it requires cert-manager
v1.0+ in the target cluster. The default install (config/default)
provisions a self-signed Issuer plus a Certificate for the webhook
serving cert, and relies on cert-manager's cert-manager.io/inject-ca-from
annotation to inject the CA bundle into the generated
MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yamlThe default Kustomize overlay brings up both control-plane components:
inference-cache-controller-manager— the reconciler + admission webhooks.inference-cache-server— the gRPC policy server (InferenceCache) and the HTTP/healthz,/readyz,/metricsprobe surface, plus a dedicated controller-facing listener carrying/snapshot(controller read),/policy(controller write), and/probe(functional self-test driven by the CacheBackend reconciler), all gated by ServiceAccount bearer auth + aNetworkPolicy. Fronted by aClusterIPServiceinference-cache-serverin theinference-cache-systemnamespace with named portsgrpc:9090(gRPC API),http:8080(probes / metrics), andsnapshot:8081(controller-only/snapshot+/policy+/probe). The controller's CacheIndex poller scrapeshttp://inference-cache-server:8081/snapshot, the CachePolicy reconciler POSTs tohttp://inference-cache-server:8081/policy, and the CacheBackend reconciler POSTs tohttp://inference-cache-server:8081/probe(default for the--server-probe-urlflag — set empty to disable the gate);/snapshot+/probesend the controller-audience projected ServiceAccount token, while/policysends the write-side policy-audience token. Once both pods are Readykubectl get cacheindexreports live cluster-wide cache state.FunctionalProbeOKappears on each managedCacheBackendonly after it clears the upstream KV-event readiness gate (i.e. real engine pods have published at least one KV event for it) — see docs/design/cachebackend-api.md#functional-probe-gate; it is intentionally not visible on a default install with no engine workload.
kubectl apply -k config/default
kubectl -n inference-cache-system wait --for=condition=Available deployment --all --timeout=180s
kubectl get cacheindex cluster-default -o yamlBoth binaries expose Prometheus metrics on their pod's :8080/metrics
(prefixed inferencecache_*) — the server binary's series cover the
in-memory index and gRPC handlers; the controller binary's series cover
the reconcilers (e.g. inferencecache_backend_probe_result_total,
inferencecache_backend_server_restart_cascades_total). A default
alert bundle for the operational silent-failure patterns this code has
hit in production ships under
config/observability/ and is not included
in config/default — the alerts are opt-in so that installs without
prometheus-operator CRDs are not affected by an unknown apiVersion.
For prometheus-operator / kube-prometheus installs:
kubectl apply -k config/observabilityThis ships THREE resources: a ServiceMonitor (so Prometheus scrapes
inference-cache-server:8080/metrics), a PodMonitor (so Prometheus
scrapes the controller pod's :8080/metrics — required for the
controller-side alerts like ServerProbeFail to have a series to
evaluate), and the PrometheusRule carrying the alerts.
Caveat — Prometheus Operator selectors. All three CRs carry example labels (
prometheus: k8s, plusrole: alert-ruleson the PrometheusRule) that match the upstream kube-prometheus stack (defaultPrometheusnamedk8s). Thekube-prometheus-stackHelm chart uses a different convention (release: <release-name>, noprometheus:label) — its rule / serviceMonitor / podMonitor selectors do not match what's shipped here. If yourPrometheusCR'sruleSelector/serviceMonitorSelector/podMonitorSelectoruses a different label set (release: my-prom, etc.),kubectl apply -ksucceeds but Prometheus silently ignores the resources. The YAML comments next to each label spell out the introspection command (kubectl get prometheus -A -o jsonpath=...); seedocs/observability/alerts.mdfor the full discussion.
Four of the five Stage 1 alerts (IndexEmpty, LookupRouteDegenerate,
LookupRouteHighTimeout, IndexEvictionsSpike) become active as soon
as the operator is installed AND the selectors match — they remain
quiet on a healthy or idle install (each rule is gated by traffic/
rate/eviction thresholds; see for: + the rate floors in the alert
expressions) and only fire when the conditions are met.
The fifth alert needs a vLLM scrape this bundle does NOT ship.
LMCacheT2NoHitsreadsvllm:external_prefix_cache_*from vLLM engine pods directly. The shippedServiceMonitorcovers onlyinference-cache-server. To make that alert effective, add a separatePodMonitorfor your vLLM Deployment (orkubernetes_sd_configs: podfor vanilla Prometheus) so engine/metricsis scraped with bothnamespaceandpodlabels attached. See alerts.md "How to enable" for the requirement.
For vanilla Prometheus, ConfigMap mounts, or Helm prometheus.serverFiles,
use the flat alerting-rules.yaml.
You must also configure scraping yourself, for BOTH the server AND
the controller pod. The server's :8080 exposes the index, lookup,
and auth series; the controller pod's :8080 exposes the per-stage
probe-result counter (inferencecache_backend_probe_result_total)
and the cache-server restart-cascade counter — the controller-side
alerts (ServerProbeFail today) load against the controller's
series, so a server-only scrape leaves them inert.
For multi-install or per-install isolation, use Kubernetes service
discovery (kubernetes_sd_configs: pod or endpoints) with
relabel_configs: that copies __meta_kubernetes_namespace to
namespace — the alerts scope per install by that label. A pair of
static DNS scrapes (e.g.
inference-cache-server.inference-cache-system.svc.cluster.local:8080
PLUS pod-IP discovery for the controller manager pods, which have no
Service in front of them) is acceptable for a single install but
loses per-install isolation; do NOT use it if you scrape multiple
inference-cache installs into one Prometheus. Without a working
scrape on both endpoints, the rules load but fire on nothing (server
alerts) or load but never have a series to evaluate (controller
alerts like ServerProbeFail).
Per-alert runbooks (causes, triage steps, example PromQL): see
docs/observability/alerts.md. For the
underlying metric surface, see
docs/reference/metrics.md.
Create a kind cluster for controller development:
make dev-clusterBy default this creates or reuses a cluster named inference-cache. You can override the name and node image:
make dev-cluster KIND_CLUSTER=cache-dev KIND_NODE_IMAGE=kindest/node:v1.31.0make build: build the controller, server, kvevent-subscriber, and inferencecache binariesmake test: run unit testsmake lint: run gofmt and go vetmake sbom,make sbom-release,make sbom-images,make sbom-registry-images: generate SPDX JSON SBOMs (seedocs/operations/sbom.md)make ci-lint: run golangci-lintmake verify-prometheus: lint + unit-test the Prometheus alerting rules underconfig/observability/make proto-gen: regenerate protobuf Go codemake generate: regenerate Kubernetes deepcopy codemake manifests: regenerate CRD, RBAC, and webhook manifestsmake image-build: build controller, server, and kvevent-subscriber images
Docs live under docs/:
- Concepts —
docs/concepts/: engine binding, engine overrides, policy tuning, tenant identity/quota, and the CacheIndex cluster aggregate. - Design —
docs/design/: theCacheBackendCRD contract, theInferenceCachegRPC contract, gRPC TLS, policy CRDs, LookupRoute ranking, and KV-event subscriber wiring. - Reference —
docs/reference/: the metric surface and gRPC reason codes. - Operations & observability —
docs/operations/, the alert runbooks indocs/observability/, and theinferencecache doctorpreflight CLI. - Reference stack —
docs/reference-stack/: runnable manifests + a GPU runbook for a full vLLM/LMCache stack. - Quick start —
docs/quickstart.md.
Contributor guide: CONTRIBUTING.md (layout, naming rule, push/PR gates).
Apache-2.0