After studying this material, you should be able to:
Before understanding gisting, you need to understand what it solves.
When you deploy an LLM agent, you typically give it a system prompt — a set of instructions that defines its behavior. For example:
"You are a GraphQL agent. You help users query data.
Follow these rules: [rule 1]... [rule 2]... [rule 3]...
Here are example queries: [example 1]... [example 2]..."
This can easily reach thousands of tokens.
Every token in your prompt has a direct cost:
| Problem | Explanation |
|---|---|
| Slower inference | More tokens = more computation per request |
| Higher cost | You pay per token in most APIs |
| More GPUs needed | To serve the same traffic volume |
| Larger KV cache | Every generated token must read the entire cached sequence |
Concrete example from Shopify: Their GraphQL agent had a ~6,000 token system prompt. At 350 requests per minute, this created measurable latency and required more GPUs than necessary.
The system prompt is identical across every request. You are paying the same token cost thousands of times per minute for the same information.
Gisting compresses a long prompt into a small set of special tokens called gist tokens, which carry the same behavioral information as the full prompt — but at a fraction of the token count.
BEFORE gisting:
[6,000 token system prompt] + [user message] → Model
AFTER gisting:
[1,500 gist tokens] + [user message] → Model
Same model behavior. 4× fewer tokens.
A gist token is:
Think of it like a zip file for meaning — the compressed version unpacks to the same information when the model processes it.
At 4:1 compression, one gist token replaces four original prompt tokens:
Original: [tok1][tok2][tok3][tok4] → [gist_1]
Original: [tok5][tok6][tok7][tok8] → [gist_2]
...and so on
6,000 tokens → 1,500 gist tokens
This is the most technically important part. The gist embeddings are not hand-crafted — they are learned through a process called knowledge distillation.
Knowledge distillation is a technique where a student model learns to mimic a teacher model. Here, both teacher and student are the same model — the difference is what they see as input.
Training runs two forward passes on each example:
Input: [Full natural-language system prompt] + [conversation]
Output: Teacher logits (probability distribution over next tokens)
The model sees the real, full prompt and produces predictions. These predictions are the ground truth we want to replicate.
Input: [Gist tokens] + [conversation]
Output: Student logits (probability distribution over next tokens)
The model sees only the compressed gist tokens and produces its own predictions.
We measure the difference between teacher and student predictions using KL divergence — a mathematical measure of how different two probability distributions are:
Loss = KL_divergence(teacher_logits, student_logits)
We then update only the gist embeddings (not the model weights) to minimize this loss. Over many training steps, the gist embeddings learn to make the model behave as if it saw the full prompt.
Important: The model weights are frozen. Only the gist token embeddings are trained. This is efficient and preserves the model's existing capabilities.
Teacher: [Full Prompt] → [Same LLM] → Teacher Logits ─┐
├→ KL Loss → Update Gist Embeddings
Student: [Gist Tokens] → [Same LLM] → Student Logits ─┘
Once training is complete, deployment is straightforward:
Key principle: The entire cost of compression is paid once, at training time. Inference is standard.
Before: Request → [6,000 token prompt + user message] → GPU → Response
After: Request → [1,500 gist tokens + user message] → GPU → Response
You might wonder: "Can't we just cache the system prompt so we don't recompute it?" This is called prefix caching, and it's a good optimization — but it doesn't fully solve the problem.
Modern serving engines store KV (key-value) tensors for previously seen sequences. If the system prompt was seen before, its KV tensors are fetched from cache instead of recomputed.
First request: Compute KV tensors for system prompt → Store in cache
Second request: Fetch KV tensors from cache → Skip recomputation ✓
Prefix caching eliminates recomputation but not memory reads.
Every time the model generates a new token, that token must attend over every key in the sequence — cached or not. This means:
For each generated token:
Read entire KV cache from memory (including cached system prompt)
Memory read size grows linearly with sequence length
This is called being memory-bandwidth-bound — the bottleneck is how fast you can read data from memory, not how fast you can compute.
| Optimization | Eliminates Recomputation | Reduces Memory Reads |
|---|---|---|
| Prefix Caching | ✅ Yes | ❌ No |
| Gisting | ✅ Yes (fewer tokens to compute) | ✅ Yes (smaller KV cache) |
| Both Together | ✅ Yes | ✅ Yes (maximum benefit) |
Shopify uses both in production. The optimizations compound — they don't cancel each other out.
Shopify used an autoresearch loop (automated hyperparameter tuning) to discover these critical optimizations:
Bad approach: Initialize gist embeddings with random noise Good approach: Initialize each gist embedding with the mean of the corresponding chunk of the original prompt
Prompt chunk 1: [tok1, tok2, tok3, tok4]
Gist token 1 initialized = mean_embedding([tok1, tok2, tok3, tok4])
Prompt chunk 2: [tok5, tok6, tok7, tok8]
Gist token 2 initialized = mean_embedding([tok5, tok6, tok7, tok8])
Result: Initial loss reduced by a factor of 7×. The embeddings start close to the right answer instead of from scratch.
Not all compression ratios work equally well. Too aggressive = quality loss. The team tested multiple ratios and found 4:1 was optimal for their domain complexity.
Different domains may have different optimal ratios. A simple prompt might compress more; a complex technical prompt might compress less.
The training dataset needs to be:
This closed the remaining quality gap after the other optimizations.
Wrong: Average loss per response token → caused hallucinations Right: Average loss over the entire batch → preserved signal from long responses, produced stable embeddings
Result: Training time cut from 30 hours to 6 hours
| Metric | Full Prompt | Gisted | Improvement |
|---|---|---|---|
| Median TTFT | 438ms | 354ms | ↓ 19% |
| Median E2E Latency | 6.8s | 4.2s | ↓ 38% |
| Throughput | 20.2 QPS | 23.4 QPS | ↑ 16% |
| GPUs Required | Baseline | −14% | Fewer GPUs |
At higher concurrency, batches get larger. Larger batches mean more KV cache reads per step. Since gisting reduces KV cache size, the benefit compounds as load increases.
Gisting integrates cleanly into ongoing model improvement:
1. Train gist embeddings via distillation
2. Deploy gisted model
3. Collect new data from production
4. Post-train using gist tokens as prefix
5. Update BOTH model weights AND gist embeddings on new data
6. No need to re-distill from scratch
Benefit: You can continuously improve the model without paying the full distillation cost each time. The gist embeddings evolve alongside the model weights.
GISTING IN ONE DIAGRAM:
Training Time (paid once):
Full Prompt (6,000 tokens)
↓ Knowledge Distillation
Gist Embeddings (1,500 tokens)
↓ Written into model
Updated Model
Inference Time (paid every request):
[1,500 gist tokens] + [user message]
↓
Same model, same behavior
↓
Response
Result: 4× fewer tokens, same quality
What is a gist token and how does it differ from a regular token?
A gist token is a special token with a learned embedding that encodes the meaning of multiple original prompt tokens. Unlike regular tokens whose embeddings are fixed during pretraining, gist embeddings are specifically trained to compress prompt information.
Why are model weights frozen during gist training?
To preserve the model's existing capabilities. Only the gist embeddings need to learn — the model already knows how to process information; we just need to teach it what the new tokens mean.
Why doesn't prefix caching eliminate the need for gisting?
Prefix caching eliminates recomputation but not memory reads. Every generated token still reads the full KV cache, and that read scales with sequence length. Gisting reduces the sequence length itself.
What would happen if you used too aggressive a compression ratio?
Prediction quality would degrade — the gist tokens wouldn't have enough capacity to encode all the information in the original prompt.
Why does the performance gap between gisted and non-gisted models grow at higher request concurrency?
Larger batches require more KV cache reads per step. Since gisting reduces KV cache size, the memory bandwidth savings become more significant as batch sizes grow.