微信扫码
添加专属顾问
我要投稿
揭秘Prompt Caching技术:如何让大模型推理速度提升6倍、成本降低90%? 核心内容: 1. Prompt Caching技术原理与工作流程 2. 性能对比:处理速度提升6倍,成本降低90% 3. 适用场景与具体使用方法
通过提示词缓存技术让大模型推理速度提升 n 倍、成本降低 xx%
今天学习了一下大模型的提示词缓存功能,读文档时更多的是在教学它的优势、使用方法、使用场景,
但没有告知技术原理,于是找大模型尝试了解了一下,然后干脆让大模型帮我写了一篇技术文档。
以下是大模型生成:
假设你有一本 10 万字的小说,需要基于这本书回答两个问题:
场景:
系统提示词: "以下是《傲慢与偏见》的完整内容:[10万字小说文本]"
第一次问答:
# 问题 1
User: "这本书的主题是什么?"
# API 处理
- 将 10 万字小说 + 问题分解为 100,020 个 tokens
- 逐层计算所有 tokens(40 层 Transformer)
- 创建缓存(保存计算结果)
- 返回答案
耗时: 2.5 秒
成本: $0.375第二次问答:
# 问题 2
User: "主要角色有哪些?"
# API 处理
- 发现系统提示词(10万字小说)与之前相同
- ✓ 从缓存直接加载已计算的结果(100,000 tokens)
- ✗ 仅计算新问题的部分(20 tokens)
- 返回答案
耗时: 0.4 秒(快了 6 倍!)
成本: $0.03(便宜了 90%!)Prompt Caching(提示词缓存) 是一种优化技术:
核心流程:
第一次请求: [长文本] + [问题1]
↓
完整计算所有 tokens
↓
保存计算结果到缓存 ← 创建缓存
↓
返回答案1
第二次请求: [长文本] + [问题2]
↓
检测到 [长文本] 部分相同 ← 前缀匹配
↓
✓ 加载 [长文本] 的缓存结果
✗ 仅计算 [问题2] 部分
↓
合并结果,返回答案2基于上述示例(10 万字小说 + 2 次问答):
| 处理的 tokens | ||
| 耗时 | ||
| 成本 |
Claude API 定价(Sonnet 4.5):
第一次问答(创建缓存):
Input (创建缓存): 100,020 tokens × $3.75/1M = $0.375
Output: 50 tokens × $15/1M = $0.00075
总计: $0.375第二次问答(读取缓存):
Input (读取缓存): 100,000 tokens × $0.30/1M = $0.030
Input (常规): 20 tokens × $3.00/1M = $0.00006
Output: 50 tokens × $15/1M = $0.00075
总计: $0.031节省分析:
如果没有缓存,两次问答总成本:
2 × (100,020 × $3/1M) = $0.60
使用缓存,两次问答总成本:
$0.375 + $0.031 = $0.406
节省: ($0.60 - $0.406) / $0.60 = 32%
如果是 10 次问答:
不使用缓存: 10 × $0.30 = $3.00
使用缓存: $0.375 + 9 × $0.03 = $0.645
节省: 78%在理解缓存原理之前,我们需要了解大模型是如何处理文本的。
输入文本: "这本书的主题是什么?"
↓
[1] Tokenization(分词)
将文本切分为 tokens
↓
[2] Embedding(嵌入)
每个 token 转换为高维向量(如 4096 维)
↓
[3] Transformer 层(重复 40+ 层)
│
├─ 自注意力机制(Attention)
│ ├─ 计算 Query (Q):当前关注什么
│ ├─ 计算 Key (K):提供什么信息 ← 缓存这个!
│ ├─ 计算 Value (V):具体内容是什么 ← 缓存这个!
│ └─ Attention 计算: Q 与 K/V 交互
│
└─ 前馈网络(FFN)
↓
[4] 输出层
生成下一个 token
↓
返回文本: "这本书的主题是爱情与社会阶级..."将文本切分为模型的最小处理单位 - Token:
# 示例
文本: "这本书的主题是什么?"
↓
Tokens: ["这", "本", "书", "的", "主题", "是", "什么", "?"]
↓
Token IDs: [1234, 5678, 9012, 3456, 7890, 2345, 6789, 0123]关键点:
在我们的示例中:
这是大模型最关键、最耗时的部分。
简化理解:
假设要理解"这本书的主题是什么?"这句话中"主题"这个词:
步骤 1: 为每个词计算三个向量
- Query (Q):我(主题)想查询什么信息?
- Key (K):其他词能提供什么信息?
- Value (V):其他词的具体内容是什么?
步骤 2: 计算相关性
用"主题"的 Q 与所有词的 K 计算相似度:
- "这" 的相关性:0.1
- "本" 的相关性:0.2
- "书" 的相关性:0.8 ← 高相关
- "的" 的相关性:0.1
- "主题" 的相关性:0.5
- "是" 的相关性:0.1
- "什么" 的相关性:0.3
步骤 3: 加权求和
根据相关性,将所有词的 V 加权组合:
output_主题 = 0.1×V_这 + 0.2×V_本 + 0.8×V_书 + ... + 0.3×V_什么为什么这里最耗时?
在我们的示例中(100,020 个 tokens):
单层 Transformer 需要计算:
- 100,020 个 Q 向量
- 100,020 个 K 向量 ← 这个可以缓存!
- 100,020 个 V 向量 ← 这个可以缓存!
- 100,020 × 100,020 的注意力矩阵(100 亿次计算!)
模型有 40 层:
- 总计算量 = 40 层 × 100,020 tokens = 4,000,800 次 K/V 计算
- 这就是为什么处理 10 万 tokens 需要 2-3 秒答案:缓存的是每一层 Transformer 中所有 tokens 的 Key (K) 和 Value (V) 向量。
不是缓存:
而是缓存:
以我们的示例为例(100,000 tokens 的小说):
# 缓存的完整结构
cache = {
"id": "cache_abc123",
"created_at": 1704902400,
"ttl": 300, # 5 分钟有效期
# Token 序列(用于匹配)
"tokens": [1234, 5678, 9012, ...], # 100,000 个 token IDs
# 每一层的 KV Cache
"layers": [
# Layer 0
{
"keys": [
# Token 0 的 key 向量
[k0_head0, k0_head1, ..., k0_head31], # 32 个注意力头
# Token 1 的 key 向量
[k1_head0, k1_head1, ..., k1_head31],
# ... 100,000 个 tokens
], # Shape: (100000, 32, 128)
"values": [
# 结构与 keys 相同
...
] # Shape: (100000, 32, 128)
},
# Layer 1-39 结构相同
...
]
}
# 存储大小估算(假设 float16)
每层大小 = 100,000 tokens × 32 heads × 128 dim × 2 (K+V) × 2 bytes
= 1.6 GB
40 层总大小 = 1.6 GB × 40 = 64 GB第一次请求(创建缓存):
# 处理 100,020 个 tokens(小说 + 问题1)
for layer in range(40): # 40 层
for token in all_100020_tokens: # 每个 token
# 1. 计算 K 和 V(耗时!)
k = W_k @ embedding(token) # 矩阵乘法
v = W_v @ embedding(token) # 矩阵乘法
# 2. 保存到缓存
cache.layers[layer].keys.append(k)
cache.layers[layer].values.append(v)
总计算量: 40 层 × 100,020 tokens × 2 (K和V) = 8,001,600 次计算
耗时: ~2.5 秒第二次请求(使用缓存):
# 检测到前 100,000 tokens(小说)相同
# 仅需处理新增的 20 tokens(问题2)
for layer in range(40):
# 1. 从缓存加载小说部分的 K/V(快速内存读取!)
cached_K = cache.layers[layer].keys # 100,000 个
cached_V = cache.layers[layer].values
# 2. 仅计算新问题的 K/V(仅 20 个 tokens)
new_K = []
new_V = []
for token in new_20_tokens:
k = W_k @ embedding(token)
v = W_v @ embedding(token)
new_K.append(k)
new_V.append(v)
# 3. 拼接
full_K = concat(cached_K, new_K) # (100020, 32, 128)
full_V = concat(cached_V, new_V)
# 4. 使用拼接后的 K/V 进行 Attention 计算
output = attention(Q, full_K, full_V)
总计算量: 40 层 × 20 tokens × 2 = 1,600 次计算
加速比: 8,001,600 / 1,600 = 5000 倍(仅 K/V 计算部分)
实际加速: ~6 倍(因为还有 Attention 计算等其他步骤)数学保证:
自注意力的计算公式:
Attention(Q, K, V) = softmax(Q · K^T / √d) · V
这个计算只依赖 K 和 V 的数值,不关心它们来自哪里:
# 场景 1:不使用缓存(全部计算)
K_all = compute_K([小说tokens] + [问题tokens])
V_all = compute_V([小说tokens] + [问题tokens])
result_1 = attention(Q, K_all, V_all)
# 场景 2:使用缓存(拼接)
K_cached = load_from_cache() # 小说部分
K_new = compute_K([问题tokens])
K_all = concat(K_cached, K_new) # 数值完全相同
V_cached = load_from_cache()
V_new = compute_V([问题tokens])
V_all = concat(V_cached, V_new)
result_2 = attention(Q, K_all, V_all)
# 验证
assert result_1 == result_2 # 完全相同!只要 K/V 矩阵的数值相同,无论是实时计算还是从缓存加载,最终结果都完全一致。
缓存通过精确的 token 序列前缀匹配来判断是否可以复用。
核心算法:
def find_cache_match(current_tokens, cache_db):
"""
查找与当前请求匹配的最长前缀缓存
"""
best_match = None
max_length = 0
for cache in cache_db:
# 逐个比较 token
match_length = 0
for i in range(min(len(current_tokens), len(cache.tokens))):
if current_tokens[i] == cache.tokens[i]:
match_length += 1
else:
break # 遇到不同就停止
# 必须 >= 1024 tokens 才能缓存
if match_length >= 1024 and match_length > max_length:
max_length = match_length
best_match = cache
return best_match, max_length第一次问答(创建缓存):
发送到 API:
System: [小说 100,000 tokens] + cache_control
User: [问题1 20 tokens] + cache_control
Token 序列:
[t1, t2, t3, ..., t100000, t100001, ..., t100020]
└──────── 小说 ────────┘ └──── 问题1 ────┘
创建缓存:
Cache_1.tokens = [t1, t2, ..., t100020]
Cache_1.layers[0-39] = 所有 tokens 的 K/V 矩阵第二次问答(匹配缓存):
发送到 API:
System: [小说 100,000 tokens] + cache_control ← 与第一次相同
User: [问题2 20 tokens] + cache_control ← 不同的问题
Token 序列:
[t1, t2, t3, ..., t100000, t200001, ..., t200020]
└──────── 小说 ────────┘ └──── 问题2 ────┘
↑ 前 100,000 个 tokens 完全相同
匹配过程:
1. 比较 Cache_1.tokens 与当前 tokens
2. 前 100,000 个完全匹配 ✓
3. 第 100,001 个开始不同 ✗
结果:
- 匹配长度: 100,000 tokens
- 从缓存加载: 前 100,000 个 tokens 的 K/V
- 重新计算: 后 20 个 tokens (问题2)在代码中,我们用 cache_control 标记缓存点:
response = client.messages.create(
system=[{
"type": "text",
"text": book_content, # 10万字小说
"cache_control": {"type": "ephemeral"} # ← 缓存到这里
}],
messages=[{
"role": "user",
"content": [{
"type": "text",
"text": "这本书的主题是什么?",
"cache_control": {"type": "ephemeral"} # ← 可选的第二个缓存点
}]
}]
)缓存点规则:
如果只在 System 添加 cache_control:
缓存内容 = [System 全部内容]
如果同时在 System 和 User 添加:
缓存内容 = [System 全部内容] + [之前的所有 messages] + [当前 User]关键:缓存点标记的是"从开头到这里"的所有内容,不是某一段!
# ❌ 错误理解:只缓存这一段
system = [
{"type": "text", "text": "Part A"},
{"type": "text", "text": "Part B", "cache_control": {...}}, # 错误!
{"type": "text", "text": "Part C"}
]
# Part C 不会被缓存
# ✓ 正确:缓存从开头到标记点的所有内容
system = [
{"type": "text", "text": "Part A"},
{"type": "text", "text": "Part B"},
{"type": "text", "text": "Part C", "cache_control": {...}} # 正确!
]
# 缓存 Part A + Part B + Part C请求 1: [━━━━━━━ 小说 100K ━━━━━━━][问题1 20]*
创建 Cache_1: 100,020 tokens
请求 2: [━━━━━━━ 小说 100K ━━━━━━━][问题2 20]*
匹配检测:
[━━━━━━━ 小说 100K ━━━━━━━] ← 与 Cache_1 完全匹配 ✓
└─────── 从缓存加载 ──────┘
[问题2 20] ← 重新计算 ✗
结果:
- Cache Read: 100,000 tokens
- Input: 20 tokens
- 总耗时: 0.4 秒import anthropic
client = anthropic.Anthropic(api_key="your_api_key")
# 第一次请求
response_1 = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
system=[{
"type": "text",
"text": book_content, # 长文本内容
"cache_control": {"type": "ephemeral"} # ← 标记缓存点
}],
messages=[{
"role": "user",
"content": "这本书的主题是什么?"
}]
)
# 查看缓存统计
print(f"Input tokens: {response_1.usage.input_tokens}")
print(f"Cache write: {response_1.usage.cache_creation_input_tokens}")
print(f"Cache read: {response_1.usage.cache_read_input_tokens}")
# 输出:
# Input tokens: 100020
# Cache write: 100020 ← 创建了缓存
# Cache read: 0 ← 第一次没有缓存可读
# 第二次请求(5分钟内)
response_2 = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
system=[{
"type": "text",
"text": book_content, # 相同的长文本
"cache_control": {"type": "ephemeral"}
}],
messages=[{
"role": "user",
"content": "主要角色有哪些?" # 不同的问题
}]
)
# 查看缓存统计
print(f"Input tokens: {response_2.usage.input_tokens}")
print(f"Cache write: {response_2.usage.cache_creation_input_tokens}")
print(f"Cache read: {response_2.usage.cache_read_input_tokens}")
# 输出:
# Input tokens: 20 ← 只计算新问题
# Cache write: 100020 ← 更新缓存
# Cache read: 100000 ← 从缓存读取!ephemeral |
注意:
def analyze_cache_usage(response):
"""分析缓存使用情况"""
usage = response.usage
# 计算总输入
total_input = usage.input_tokens + getattr(usage, 'cache_read_input_tokens', 0)
# 缓存命中率
cache_hit_rate = (
getattr(usage, 'cache_read_input_tokens', 0) / total_input * 100
if total_input > 0 else 0
)
# 成本估算
cost = (
usage.input_tokens * 3.00 / 1_000_000 +
getattr(usage, 'cache_read_input_tokens', 0) * 0.30 / 1_000_000 +
getattr(usage, 'cache_creation_input_tokens', 0) * 3.75 / 1_000_000 +
usage.output_tokens * 15.00 / 1_000_000
)
print(f"总输入: {total_input:,} tokens")
print(f" ├─ 从缓存: {getattr(usage, 'cache_read_input_tokens', 0):,} ({cache_hit_rate:.1f}%)")
print(f" └─ 新计算: {usage.input_tokens:,}")
print(f"缓存写入: {getattr(usage, 'cache_creation_input_tokens', 0):,}")
print(f"估算成本: ${cost:.6f}")
# 使用
analyze_cache_usage(response_2)
# 输出:
# 总输入: 100,020 tokens
# ├─ 从缓存: 100,000 (99.9%)
# └─ 新计算: 20
# 缓存写入: 100,020
# 估算成本: $0.030081✅ 长文档问答
✅ 固定提示词的应用
✅ 批量处理
❌ 单次请求
❌ 短文本
❌ 动态内容
是否有长文本(> 1K tokens)?
├─ 否 → ❌ 不使用缓存
└─ 是 → 是否有多次请求?
├─ 否 → ❌ 不使用缓存
└─ 是 → 内容是否相对固定?
├─ 否 → ❌ 不使用缓存
└─ 是 → ✅ 使用缓存!cache_control: {type: "ephemeral"}cache_read_input_tokens 确认命中
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-01-30
Skills 元年,一人公司的时代要来了:速通 Anthropic 通识课
2026-01-30
Claude Skills 背后的原理解析
2026-01-30
实测 Skills:用planning-with-files 做技术预研助手
2026-01-30
Subagent 与 Skills 的本质
2026-01-30
《一篇把 Claude Skill 讲透的工程化指南》
2026-01-29
Qoder CLI Skills 实战指南及常用 Skills 分享
2026-01-29
Claude Skills 如何在 90 天内成为 AI 最重要的标准
2026-01-29
从“能用”到“会用”|如何写好一个 Skill
2025-11-20
2026-01-04
2025-11-15
2026-01-13
2025-11-15
2025-12-02
2025-11-12
2025-11-15
2025-11-03
2025-11-16
2026-01-23
2026-01-19
2026-01-19
2026-01-15
2026-01-05
2025-12-30
2025-12-26
2025-12-15