HomeResourcesCase study
Case study

Character.AI's Real Bottleneck Wasn't Compute

By The SDL team·4 min read·Updated Sep 16, 2026

Serving an LLM cheaply is a memory problem, not a math problem. The KV-cache is the wall everyone forgets.

Everyone assumes serving an LLM is bottlenecked by raw compute — by how fast the GPU can multiply matrices. Character.AI's engineers will tell you the real ceiling is something almost nobody mentions: memory for remembering what was already said.

Character.AI serves around 20,000 queries per second at a cost of less than one cent per hour of conversation — and they got there by attacking not the model's math, but its memory. Specifically, a structure called the KV-cache, which turned out to be the thing standing between them and affordable scale.

Plain English

When an LLM generates text, it produces one token (roughly, one word-piece) at a time, and each new token depends on everything that came before. To avoid re-reading the entire conversation from scratch for every single token, the model stores a summary of the prior context — the KV-cache (key/value cache) — in fast GPU memory.

Here's the squeeze: that cache grows with the length of the conversation and with the number of people chatting at once. GPU memory is finite and expensive. So the number of conversations you can serve simultaneously isn't limited by how fast the chip computes — it's limited by how many KV-caches you can fit in memory. Throughput is memory-bound, and almost everyone's intuition says it should be compute-bound.

Why the obvious lever is the wrong one

Faced with "we can't serve enough users," the instinct is buy more GPUs or a faster GPU. But if each conversation hogs memory, a faster chip just sits idle waiting — you've paid for compute you can't use because memory filled first. The leverage isn't in the processor. It's in shrinking the KV-cache so more conversations fit in the memory you already have.

The KV-cache, not compute, is what caps how many chats you can serve GPU memory budget, per server: Before optimisation model weights KV-cache (grows with every token, every user) → huge KV-cache means few concurrent conversations fit. Throughput is memory-bound. After: multi-query attention + cross-layer KV sharing + int8 quantization model weights KV freed memory → many more concurrent chats KV-cache cut > 20×. Plus: cache the prefix between turns (RadixAttention-style) so a conversation doesn’t re-process its own history every message. Result: ~20,000 queries/sec at < 1¢ per hour of conversation — a 33× reduction in serving cost since 2022.
Memory is the budget. Shrink the KV-cache and the freed memory translates directly into more concurrent conversations on the same hardware.
Now the engineering

Character.AI stacked three techniques to cut the KV-cache by more than 20x:

Multi-query attention. Standard attention keeps a separate set of keys and values for every attention "head." Multi-query attention shares one set of keys/values across all heads, slashing the per-token memory footprint with minimal quality loss.

Cross-layer KV sharing. A model has many layers, each normally storing its own KV state. Character.AI shares KV state across layers, cutting the cache further.

Int8 quantization. Store the cache (and weights) using 8-bit integers instead of 16-bit floats — roughly half the memory for each cached value.

Then a fourth idea attacks repeated work. In a chat, every new message re-includes the whole conversation history. Naively, the model re-processes that shared prefix every turn. Using a RadixAttention-style prefix cache, Character.AI keeps the already-computed KV state for the shared conversation prefix between turns — so a long conversation doesn't pay to re-read itself on every message. This is, in spirit, the same insight as semantic caching: don't recompute what hasn't changed.

>20x
KV-cache reduction
~20k/s
queries served
33x
cheaper serving since 2022

Worth knowing

The headline "<1¢ per hour of conversation" is what makes consumer-scale conversational AI economically viable at all. And it came almost entirely from memory engineering, not from a better model or more silicon. When a workload is memory-bound, every byte you save in the hot structure converts directly into throughput — which is why the KV-cache, not the GPU, was the right thing to obsess over.

The gap it reveals

As LLMs enter every system design, the naive mental model is "it's a compute problem, throw GPUs at it." The 2026-relevant realisation is that LLM serving is dominated by KV-cache memory, and that the highest-leverage optimisations (multi-query attention, KV sharing, quantization, prefix caching) all target memory footprint. Engineers who understand this reason about AI infrastructure correctly; everyone else reasons about a problem that isn't the bottleneck.

In the interview room

"Design a chatbot serving system" is the new hotness in design rounds, and the discriminating move is to identify the bottleneck correctly: "throughput here is bound by KV-cache memory, not compute, so I'd focus on shrinking the per-conversation cache and caching shared prefixes between turns." Most candidates will talk about model size and latency; naming the memory wall sets you apart immediately.

The reframe

Every new technology arrives wrapped in a wrong intuition about where its limits are. With LLMs, the wrong intuition is "compute." The real constraint — for serving, at least — is the memory cost of context. Character.AI's edge wasn't a secret model; it was correctly identifying which resource was actually scarce and engineering relentlessly against that one.

The GPU was never the bottleneck. Remembering the conversation was.

Primary source →
blog.character.ai — Optimizing AI Inference at Character.AI

Want feedback on your design?

The weekly teardown

One real-world architecture, every week.

How real companies actually built it: the design, the trade-offs, and what to say about it in an interview. Free, and one click to unsubscribe.

Related articles