Scaling a High-Traffic LLM Chat Application to 30,000 Messages Per Second

character.ai · Tech · 2023 · source Agentic: no · promotional bias: low · source-backed · extracted with prompt 0.5.6 (schema v2)

Generated from extraction.json — do not edit by hand. Every claim cites verbatim source evidence; stated means the quote directly asserts the claim, implied means it supports it indirectly.

Problem

Domain: High-scale consumer LLM chat serving

Character.ai needed to scale a consumer LLM chat product from hundreds to tens of thousands of messages per second while controlling GPU cost, latency, prompt complexity, cache correctness, and database reliability.

Constraints:

Architecture

Character.ai built and served its own foundation models, allowing upstream model architecture decisions such as multi-query attention. The serving hot path relies on GPU attention-cache reuse with a 95% cache-hit ratio, prompt templates managed through Prompt Poet's YAML/Jinja layer with smart truncation priorities, and a migration from monolith toward services and sharded databases. The design optimizes for the tight cost-latency-quality tradeoff of very high-volume conversational generation.

Diagram confidence: reconstructed (reconstructed from prose).

flowchart LR
  A["User chat request"] --> B["Prompt Poet templating"]
  B --> C["Model serving layer"]
  C --> D["GPU attention cache"]
  C --> E["Streaming response connection"]
  E --> A
  C --> F["Database services"]
  G["Experiment monitoring"] --> B
Drag to pan · scroll or use +/− to zoom · reconstructed from the source's described component topology

Components

Component Role Interacts with
User chat request Incoming message plus prior character and conversation context. Prompt Poet templating
Prompt Poet templating YAML/Jinja prompt construction, runtime interpolation, control flow, and truncation priorities. Model serving layer
Model serving layer Character.ai foundation-model serving path optimized for conversation generation. GPU attention cache, Streaming response connection, Database services
GPU attention cache Caches computation for prior prompt context so only new suffix tokens need fresh work. Model serving layer
Streaming response connection Long-lived connection carrying first chunks and full generated responses to users. User chat request
Database services Persistent chat, post, and metadata storage that had to survive extreme query volume and sharding. Model serving layer
Experiment monitoring Tracks performance, engagement, time spent, and message counts for prompt/model changes. Prompt Poet templating

Data flow

A user message enters the chat hot path with prior character definitions and conversation history. Prompt Poet constructs a structured prompt and truncates lower-priority sections when context overflows. The serving layer reuses cached computations for prior context, computes only the new user-message suffix where possible, runs the model, streams a response over a long-lived connection, and writes/reads chat state through database services that had to evolve from monolithic access toward sharded service boundaries.

3 supporting quotes
“for every message that we generate we cache at each layer in the model the result of the computation”transcript, stated
“we built this tool called prompt poets which allowed us to basically templa our prompts in this yaml and ginger layer”transcript, stated
“going from monolith to Services via the shotting strategy”transcript, stated

Why this architecture

Character.ai's ownership of pretraining and post-training let it optimize model architecture for serving economics. Multi-query attention reduced GPU cache pressure, cache reuse made 30,000 messages per second financially survivable, and structured prompt templating reduced bugs when evolving multi-character chat prompts.

3 supporting quotes
“we had our own pre-training through posttraining um Stacks which meant that you know we could make architectural decisions um like multiquery attention”transcript, stated
“if we didn't have any GPU caching at all and we have like a pretty um comprehensive scaling inference part one part two on our research blog if you're curious um we we would we would be bankrupt like tomorrow”transcript, stated
“we built this tool to to S of solve the problem of iterating on the prompting layer”transcript, stated

Models used: Character.ai foundation models · Multi-query attention model variants

Scale

Alternatives considered (roads not taken)

Over-provision GPUs to reduce latency

Rejected because: It lowers latency but dramatically increases serving cost.

“you can you can pay more money in in over provisioning servers like over over provisioning gpus um to lower the latency”transcript, stated

Use a fixed model architecture supplied externally

Rejected because: Owning the model stack let Character.ai choose multi-query attention upstream for serving economics.

“unlike probably most apps building in the space you guys were building your own models from day Zero like Foundation models not fine-tuning”transcript, stated

Continue prompt concatenation and string slicing across components

Rejected because: It made significant prompt changes nearly impossible without bugs.

“this very you know um janky way of like concatenating slicing into the prompt meant that it was almost impossible to change the prompt in any significant way”transcript, stated

Patterns

semantic-caching (serving)

Character.ai reused GPU-layer computations for unchanged prompt context across successive conversation turns.

“for every message that we generate we cache at each layer in the model the result of the computation”transcript, stated
“by utilizing this GPU cache which we now have 5x more of effectively per GPU um we can actually pick up all the computation by like the 5%”transcript, stated

latency-optimization (serving)

The system optimized latency through model architecture, cache reuse, and serving-path improvements rather than only buying more GPUs.

“mqa um gave us um what's mqa mqa multier attention um it gave us 5x reduction in um in in out like GPU cache”transcript, stated
“the total response time is I think p50 is like 12.5 seconds today”transcript, stated

cost-aware-routing (economics)

Serving architecture choices were driven by the cost of GPU inference at 30,000 messages per second.

“to serve 30,000 messages per second costs a ton of money in GPU spend”transcript, stated
“cost is is like the second biggest difference is like to serve the core user experience there's a whole bunch of engineering challenges”transcript, stated

prompt-versioning (prompting)

Prompt Poet made prompt construction structured, experimentable, and safer to modify at scale.

“we built this tool called prompt poets which allowed us to basically templa our prompts in this yaml and ginger layer”transcript, stated
“when we make a change to like even a trivial change like um you know um a formatting change like using uh colon space we want at this scale like we want to know what the performance impact was”transcript, stated

context-compaction (context)

Structured YAML prompt sections carried truncation priorities so context overflow removed less important content first without destroying cache reuse.

“we don't have an infinite context window and when we sort of have context overflow we want to truncate we want to truncate in a Smart Way”transcript, stated
“we can assign a truncation priority to each part of the yaml so that we can you know take out less important things from The Prompt first”transcript, stated

Tradeoffs

Latency versus GPU spend

Chose architecture and cache optimization over only over-provisioning GPUs.

Rationale: Over-provisioning would improve latency but at unacceptable cost for 30,000 messages per second.

“over provisioning gpus um to lower the latency and we all know that like consumer number one is like better latency”transcript, stated
“but that costs a huge amount of money and so what else can you do you can like um prun prune the number of parameters”transcript, stated

Model ownership

Chose build foundation models from day zero over serving a fixed external architecture.

Rationale: Owning the pretraining stack allowed serving-oriented model architecture decisions such as multi-query attention.

“you guys were building your own models from day Zero like Foundation models not fine-tuning”transcript, stated
“actually make pretty significant architectural decisions way Upstream in the in the pre-training process”transcript, stated

Database incident response

Chose fix-forward with partial vacuum over rollback to a previous day's backup.

Rationale: Rollback would have lost a day of user data; fix-forward avoided that at the cost of a 30-40 minute outage.

“do we roll back to yesterday's backup and say that you've lost like to the users that you've lost a day's worth of data do we try and fix forward”transcript, stated
“took the side down for like 30 40 minutes”transcript, stated

Gotchas (what bit them)

Cache-indexing bugs can preserve syntax while destroying quality.

Root cause: A serving rewrite missed indexing computation results for user messages, so the 95% cached context no longer represented the real prompt.

Resolution: The team traced the issue to cache indexing and fixed it with a small code change.

“we missed indexing the the results of the computation for um user messages”transcript, stated
“it was like a probably like a three line change um to to fix the buck”transcript, stated

Prompt construction across services became fragile.

Root cause: Prompt strings were concatenated and sliced across multiple components and databases.

Resolution: Build Prompt Poet with YAML/Jinja templates, runtime variables, control flow, and truncation priorities.

“we would build part of the prompt and like via you know grpc protuff we send to a separate component and we get some more data from a different database”transcript, stated
“we built this tool called prompt poets which allowed us to basically templa our prompts in this yaml and ginger layer”transcript, stated

The database layer became the Achilles heel at scale.

Root cause: High query volume and write characteristics triggered a low-level PostgreSQL transaction-ID bug.

Resolution: The team evaluated rollback versus fix-forward, ran a partial vacuum, and took the site down briefly rather than losing a day of data.

“the other like the Achilles heel of scaling this product was the database layer 100%”transcript, stated
“we had 16 hours left of issuing DM like xids until the all the entire xid space had been had be consumed”transcript, stated

Evaluation

Character.ai evaluated changes through production engagement and performance experiments, watching latency, cost, engagement, time spent, messages sent, A/B treatment quality, and cache behavior.

Metrics: 300 generations per second to over 30,000 messages per second · Roughly two orders of magnitude growth in about 18 months · Seven or eight GB/s on the generation hot path · 95% cache-hit ratio · P50 full response around 12.5 seconds, down from around 25 seconds · Prompt changes evaluated for performance, engagement, time spent, and messages sent

“what the performance impact was the engagement impact like was it did it regress some quality somewhere was what happened to time spent happened to number messages sent”transcript, stated
“we found that like not only were the results like neutral to negative but like they were very negative”transcript, stated

Outcomes

The system handled over 30,000 messages per second, seven or eight GB/s on the generation path, long-lived 12.5-second P50 responses, and a 95% cache-hit ratio.

“went from I think like 300 uh Generations per second to this summer we went we over 30,000 mes per second”transcript, stated
“mqa um gave us um what's mqa mqa multier attention um it gave us 5x reduction in um in in out like GPU cache”transcript, stated