
在大模型的推理过程中,KV Cache 是专门为Transformer注意力机制设计的中间结果缓存技术。我们先回归Transformer 的核心:自注意力机制(Self-Attention),它的计算逻辑是:输入文本会被转换成三个向量:Query(查询向量 Q)、Key(键向量 K)、Value(值向量 V),注意力分数 = Q×Kᵀ,再通过Softmax归一化后与V相乘,得到最终的注意力输出。
大模型推理有一个关键特性:逐词生成。比如生成“今天天气很好”,模型会先输出“今”,再输出“天”,逐字迭代。如果不做缓存,每生成一个新词,都要重复计算之前所有文本的K和V向量,生成第 1000个词时,要重新计算前999个词的K/V,生成第1001个词时,又要重新计算前1000个词的K/V,这会造成海量的重复计算,推理速度极慢。
KV Cache的核心作用就是:把每一层Transformer计算好的K、V向量永久缓存下来,后续生成新词时,直接读取缓存,不再重复计算。这是大模型推理速度提升10~100倍的核心技术,没有KV Cache,当前的大模型几乎无法实现实时交互。
举个通俗的例子:我们写一篇1000字的文章,每写一个字都要把前面999个字重新写一遍,效率极低;而 KV Cache就像把前面写好的字存档,新字直接接在后面,不用重复书写。

KV Cache 的标准执行流程,我们以“逐词生成”为核心,拆解KV Cache的标准执行步骤,这是所有优化技术的基础:

整个过程中,计算量大幅降低,但显存占用会线性增长,这就是KV Cache的双刃剑特性:提速的同时,带来了显存爆炸的风险。

3.1 计算量对比柱状图
3.2 推理时间曲面
3.3 逐步生成累积效果

3.4 更直观的数值对比
KV Cache通俗解读,想象我们在写一篇长文章:
这就是KV Cache的核心价值:记住之前算过的,避免重复计算!
import torch
import torch.nn as nn
import math
# 基础自注意力模块 + 原生KV Cache
class BasicAttentionWithKVCache(nn.Module):
def __init__(self, embed_dim=128, num_heads=4):
super().__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
# Q/K/V 投影层
self.q_proj = nn.Linear(embed_dim, embed_dim)
self.k_proj = nn.Linear(embed_dim, embed_dim)
self.v_proj = nn.Linear(embed_dim, embed_dim)
self.out_proj = nn.Linear(embed_dim, embed_dim)
# 初始化KV Cache (batch_size, num_heads, seq_len, head_dim)
# 就像一张白纸,等着记录内容
self.k_cache = None
self.v_cache = None
def forward(self, x):
batch_size, seq_len, _ = x.shape
# 步骤1:计算Q/K/V向量
q = self.q_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
k = self.k_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
v = self.v_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 步骤2:KV Cache核心逻辑 - 追加缓存
#
# 【关键逻辑】
# 如果缓存里有内容(不是第一次),就把新的K/V拼到后面
# 就像写日记:今天的内容接在昨天后面,不用重写昨天的
#
if self.k_cache is not None:
k = torch.cat([self.k_cache, k], dim=2) # 维度2是序列长度
v = torch.cat([self.v_cache, v], dim=2)
# 更新缓存 - 把拼接好的存起来,下次继续用
self.k_cache = k
self.v_cache = v
# 步骤3:计算注意力分数
attn_scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
attn_probs = torch.softmax(attn_scores, dim=-1)
# 步骤4:注意力输出
attn_output = torch.matmul(attn_probs, v)
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.embed_dim)
return self.out_proj(attn_output)
# 测试代码
if __name__ == "__main__":
print("=" * 60)
print(" KV Cache 效果演示")
print("=" * 60)
# 初始化模型
attention = BasicAttentionWithKVCache(embed_dim=128, num_heads=4)
# ========================================
# 场景1:输入Prompt(首次推理)
# ========================================
print("\n【场景1】你输入了一个问题:")
print(" '今天天气怎么样,适合去哪里玩?'")
print(" 假设这句话被分成了5个token")
# 模拟输入:batch_size=1, seq_len=5, embed_dim=128
prompt_input = torch.randn(1, 5, 128)
# 第一次推理(写入缓存)
output1 = attention(prompt_input)
print(f"\n → 初始缓存形状 - K: {attention.k_cache.shape}")
print(" 含义: [1个批次, 4个注意力头, 5个token, 每头32维]")
print(" 通俗: 记住了你输入的5个字的内容")
# ========================================
# 场景2:生成第一个词(使用缓存)
# ========================================
print("\n【场景2】模型开始回答,生成第1个字:")
print(" '今' → 模型想回答'今天天气不错...'")
print(" 这时候只需要处理这1个新字!")
# 生成新词:seq_len=1(逐词生成)
new_token = torch.randn(1, 1, 128)
output2 = attention(new_token)
print(f"\n → 更新后缓存形状 - K: {attention.k_cache.shape}")
print(" 变化: 5 → 6 (增加了1个token)")
print(" 通俗: 新写的字自动接在后面,之前5个字不用重算")
# ========================================
# 场景3:继续生成(缓存持续增长)
# ========================================
print("\n【场景3】继续生成回答,第2、3、4个字...")
for i in range(3):
new_token = torch.randn(1, 1, 128)
output = attention(new_token)
current_len = attention.k_cache.shape[2]
print(f" 生成第{i+2}个字 → 缓存长度: {current_len}")
print(f"\n → 最终缓存形状 - K: {attention.k_cache.shape}")
print(" 总共记住了9个token的内容")技术细节:
输出结果:
============================================================ KV Cache 效果演示 ============================================================ 【场景1】你输入了一个问题: '今天天气怎么样,适合去哪里玩?' 假设这句话被分成了5个token → 初始缓存形状 - K: torch.Size([1, 4, 5, 32]) 含义: [1个批次, 4个注意力头, 5个token, 每头32维] 通俗: 记住了你输入的5个字的内容 【场景2】模型开始回答,生成第1个字: '今' → 模型想回答'今天天气不错...' 这时候只需要处理这1个新字! → 更新后缓存形状 - K: torch.Size([1, 4, 6, 32]) 变化: 5 → 6 (增加了1个token) 通俗: 新写的字自动接在后面,之前5个字不用重算 【场景3】继续生成回答,第2、3、4个字... 生成第2个字 → 缓存长度: 7 生成第3个字 → 缓存长度: 8 生成第4个字 → 缓存长度: 9 → 最终缓存形状 - K: torch.Size([1, 4, 9, 32]) 总共记住了9个token的内容
示例说明:
- 1. 形状变化对比:
步骤 | 输入长度 | 缓存总长度 | 计算方式 |
|---|---|---|---|
Prompt | 5 tokens | 5 tokens | 全量计算 |
生成第1字 | 1 token | 6 tokens | 只算1个新字 |
生成第2字 | 1 token | 7 tokens | 只算1个新字 |
生成第3字 | 1 token | 8 tokens | 只算1个新字 |
... | ... | ... | ... |
- 2. 核心价值:

当下的大模型已经从短文本对话升级为长上下文理解:
这些场景对模型的上下文长度提出了极致要求,但原生KV Cache完全无法支撑。
我们用数据直观感受:以7B参数大模型、128层、128头、头维度128、FP16数据类型为例:
而主流消费级GPU显存仅为24GB,高端A100也仅为80GB,原生KV Cache在10K Token就会触发显存爆炸,直接导致推理崩溃。这就是KV Cache优化的必要性:不优化,大模型就无法实现长上下文推理。
2.1 显存占用线性增长
2.2 无差别缓存,浪费严重
2.3 无共享、无分层,资源利用率极低

1.1 核心原理
量化压缩是最基础、性价比最高的KV Cache优化技术,核心逻辑是:降低K/V向量的数据精度,在几乎不损失模型效果的前提下,减少显存占用。
原生KV Cache默认使用FP16(2字节/数值)或FP32(4字节/数值),而量化技术可以将其压缩为INT8(1字节/数值)、INT4(0.5字节/数值),甚至 INT2(0.25字节/数值)。数据精度降低一半,显存占用就降低一半,推理速度也会同步提升。
1.2 技术分类
1.3 示例:INT8 KV Cache量化
import torch
# KV Cache 对称量化函数
def kv_cache_quantize(kv_cache, num_bits=8):
# 计算INT8的表示范围: -128 ~ 127
q_max = 2 ** (num_bits - 1) - 1 # 127
q_min = - (2 ** (num_bits - 1)) # -128
# 步骤1: 计算缩放因子
# 找到绝对值最大值,确定映射比例
scale = torch.max(torch.abs(kv_cache)) / q_max
# 步骤2: 量化(浮点 → 整数)
# 除以scale压缩到目标范围,四舍五入,截断越界值
kv_quantized = torch.round(kv_cache / scale).clamp(q_min, q_max).to(torch.int8)
return kv_quantized, scale
# 反量化函数(推理时使用)
def kv_cache_dequantize(kv_quantized, scale):
return kv_quantized.to(torch.float16) * scale
# 测试量化效果
if __name__ == "__main__":
print("=" * 60)
print(" KV Cache 量化效果演示")
print("=" * 60)
# 模拟真实场景:batch=1, heads=32, seq=4096, dim=128
shape = (1, 32, 4096, 128)
print(f"\n【测试配置】")
print(f" KV Cache形状: {shape}")
print(f" 总元素数: {torch.prod(torch.tensor(shape)).item():,}")
# 模拟原生KV Cache: FP16
kv_cache = torch.randn(*shape, dtype=torch.float16)
fp16_size = kv_cache.element_size() * kv_cache.nelement() / 1024**3
print(f"\n【原生FP16】")
print(f" 显存占用: {fp16_size:.2f} GB")
print(f" 数据类型: {kv_cache.dtype}")
print(f" 每元素: {kv_cache.element_size()} bytes")
# 量化为INT8
print(f"\n【量化过程】")
kv_quant, scale = kv_cache_quantize(kv_cache, num_bits=8)
int8_size = kv_quant.element_size() * kv_quant.nelement() / 1024**3
print(f" 量化位数: INT8")
print(f" 缩放因子: {scale:.6f}")
print(f" 量化后显存: {int8_size:.2f} GB")
print(f" 压缩率: {fp16_size/int8_size:.1f}x")
# 反量化验证精度
print(f"\n【精度验证】")
kv_dequant = kv_cache_dequantize(kv_quant, scale)
mse = torch.mean((kv_cache.float() - kv_dequant.float())**2)
max_error = torch.max(torch.abs(kv_cache.float() - kv_dequant.float()))
print(f" 均方误差(MSE): {mse:.8f}")
print(f" 最大误差: {max_error:.6f}")
print(f" 相对误差: {(max_error/torch.max(torch.abs(kv_cache))).item()*100:.3f}%")
# 展示具体数值示例
print(f"\n【数值示例】前5个值对比:")
sample_idx = (0, 0, 0, slice(0, 5))
print(f" 原始值: {kv_cache[sample_idx].tolist()}")
print(f" 反量化后: {kv_dequant[sample_idx].tolist()}")
print(f" 差值: {(kv_cache[sample_idx] - kv_dequant[sample_idx]).tolist()}")输出结果:
============================================================ KV Cache 量化效果演示 ============================================================ 【测试配置】 KV Cache形状: (1, 32, 4096, 128) 总元素数: 16,777,216 【原生FP16】 显存占用: 0.03 GB 数据类型: torch.float16 每元素: 2 bytes 【量化过程】 量化位数: INT8 缩放因子: 0.030746 量化后显存: 0.02 GB 压缩率: 2.0x 【精度验证】 均方误差(MSE): 0.00007889 最大误差: 0.015625 相对误差: 0.400%
技术细节:
量化总结:用轻微精度损失,换取显存大幅节省
2.1 核心原理
动态淘汰是基于注意力权重的智能缓存管理技术,核心逻辑是:计算每个Token的注意力分数,淘汰注意力权重极低的无效K/V向量,只保留关键缓存。
结合注意力机制的特性:模型只关注核心Token,次要Token的注意力权重趋近于0,剔除后完全不影响生成效果。动态淘汰会实时计算注意力分数分布,设定阈值,自动丢弃低于阈值的缓存。
2.2 执行流程

2.3 示例:注意力权重动态淘汰
import torch
# 动态淘汰函数:基于注意力权重剔除无效缓存
def dynamic_kv_evict(k_cache, v_cache, attn_probs, keep_ratio=0.7):
"""
动态淘汰低注意力权重的KV缓存
:param k_cache: 键缓存
:param v_cache: 值缓存
:param attn_probs: 注意力概率分布
:param keep_ratio: 保留比例
:return: 压缩后的KV缓存
"""
# 计算每个Token的平均注意力权重
token_attn_score = torch.mean(attn_probs, dim=(1, 2)) # [batch_size, seq_len]
# 排序:按权重从高到低
sorted_indices = torch.argsort(token_attn_score, descending=True)
# 保留高权重Token
keep_num = int(token_attn_score.shape[-1] * keep_ratio)
keep_indices = sorted_indices[:, :keep_num]
# 压缩缓存
k_cache_evicted = k_cache[:, :, keep_indices[0], :]
v_cache_evicted = v_cache[:, :, keep_indices[0], :]
return k_cache_evicted, v_cache_evicted
# 测试动态淘汰
if __name__ == "__main__":
# 模拟缓存:batch=1, heads=128, seq_len=1024, head_dim=128
k_cache = torch.randn(1, 128, 1024, 128)
v_cache = torch.randn(1, 128, 1024, 128)
attn_probs = torch.randn(1, 128, 1, 1024) # 注意力概率
print("淘汰前缓存长度:", k_cache.shape[2])
k_evict, v_evict = dynamic_kv_evict(k_cache, v_cache, attn_probs, keep_ratio=0.7)
print("淘汰后缓存长度:", k_evict.shape[2])
print("显存降低比例:", 1 - k_evict.nelement() / k_cache.nelement())输出结果:
淘汰前缓存长度: 1024 淘汰后缓存长度: 716 显存降低比例: 0.30078125
技术细节:
3.1 核心原理
大模型的Transformer层有明确的层级分工:
不同层级的K/V向量重要性完全不同,分层缓存就是为不同层级设计差异化的缓存策略,实现精细化管理。
3.2 分层策略
3.3 分层缓存架构

技术细节:
4.1 核心原理
全局共享是批量推理、多轮对话场景的核心优化技术,核心逻辑是:将不同请求、不同对话中的重复Token(如系统提示、公共文档)的K/V缓存全局共享,避免重复存储。
比如100个用户同时请求模型,都使用相同的系统提示词“你是一个智能助手”,原生KV Cache会存储100份重复的K/V向量,而全局共享只存储1份,所有请求共享读取,显存占用降低 99%。
4.2 共享机制
4.3 示例:全局共享缓存池
场景 | 差异化体现 |
|---|---|
场景1 | 首次写入,展示缓存创建过程 |
场景2 | 4个请求复用同一缓存,显示命中状态和节省显存 |
场景3 | 3个不同用户,其中2个Prompt相同,展示复用识别 |
场景4 | 统计对比:传统方式 vs 共享方式的显存差异 |
import torch
from collections import OrderedDict
# 全局共享KV缓存池
class GlobalKVCachePool:
def __init__(self, max_pool_size=10000):
self.pool = OrderedDict() # 有序字典:key=Token哈希, value=(k_cache, v_cache)
self.max_pool_size = max_pool_size
# 写入共享缓存
def put(self, token_hash, k_cache, v_cache):
if len(self.pool) >= self.max_pool_size:
self.pool.popitem(last=False) # 先进先出淘汰
self.pool[token_hash] = (k_cache, v_cache)
# 读取共享缓存
def get(self, token_hash):
return self.pool.get(token_hash, (None, None))
# 检查缓存是否存在
def exists(self, token_hash):
return token_hash in self.pool
# 测试全局共享缓存
if __name__ == "__main__":
print("=" * 65)
print(" 全局共享KV缓存池 - 效果演示")
print("=" * 65)
# 初始化缓存池
global_pool = GlobalKVCachePool(max_pool_size=100)
print(f"\n【初始化】缓存池容量: {global_pool.max_pool_size} 项\n")
# 模拟系统Prompt(所有请求共享)
sys_prompt_hash = hash("你是一个智能助手")
sys_k = torch.randn(1, 128, 8, 128)
sys_v = torch.randn(1, 128, 8, 128)
cache_size_mb = sys_k.numel() * sys_k.element_size() * 2 / 1024 / 1024
# 场景1:第一个请求写入系统Prompt缓存
print("-" * 65)
print("【场景1】请求1 - 首次系统Prompt | 提示词: '你是一个智能助手' ")
print("-" * 65)
print(f"系统Prompt缓存大小: {cache_size_mb:.2f} MB")
global_pool.put(sys_prompt_hash, sys_k, sys_v)
print(f"缓存池状态: {len(global_pool.pool)} 项")
# 场景2:多个请求复用同一缓存
print("\n" + "-" * 65)
print("【场景2】请求2-5 - 复用系统Prompt缓存")
print("-" * 65)
prompts = [
"你是一个智能助手", # 与请求1相同
"你是一个智能助手", # 与请求1相同
"你是一个智能助手", # 与请求1相同
"你是一个智能助手", # 与请求1相同
]
for i, prompt in enumerate(prompts, start=2):
prompt_hash = hash(prompt)
k, v = global_pool.get(prompt_hash)
status = "✓ 命中" if k is not None else "✗ 未命中"
saved = cache_size_mb if k is not None else 0
print(f"请求{i}: {status} | 提示词: '{prompt}' | 节省: {saved:.2f} MB")
# 场景3:不同用户不同Prompt
print("\n" + "-" * 65)
print("【场景3】用户A/B/C - 不同系统Prompt")
print("-" * 65)
users = [
("用户A", "你是编程助手"),
("用户B", "你是翻译专家"),
("用户C", "你是编程助手") # 与用户A相同
]
for user, prompt in users:
prompt_hash = hash(prompt)
k, v = global_pool.get(prompt_hash)
if k is None:
new_k = torch.randn(1, 128, 8, 128)
new_v = torch.randn(1, 128, 8, 128)
global_pool.put(prompt_hash, new_k, new_v)
print(f"{user}: 新缓存写入 | Prompt: '{prompt[:8]}...'")
else:
print(f"{user}: ✓ 复用缓存 | Prompt: '{prompt[:8]}...' (与用户A相同)")
# 场景4:统计对比
print("\n" + "=" * 65)
print("【效果统计】")
print("=" * 65)
total_saved = cache_size_mb * 4 # 4次复用
print(f"缓存池项数: {len(global_pool.pool)}")
print(f"系统Prompt复用次数: 4 次")
print(f"累计节省显存: {total_saved:.2f} MB")
print(f"传统方式需显存: {cache_size_mb * 5:.2f} MB (5份独立)")
print(f"共享方式需显存: {cache_size_mb:.2f} MB (1份共享)")
print(f"节省比例: {(1 - 1/5) * 100:.0f}%")
print("\n" + "=" * 65)
print("核心价值: N个请求共享1份缓存,显存节省 (N-1)/N")
print("=" * 65)输出结果:
================================================================= 全局共享KV缓存池 - 效果演示 ================================================================= 【初始化】缓存池容量: 100 项 ----------------------------------------------------------------- 【场景1】请求1 - 首次系统Prompt | 提示词: '你是一个智能助手' ----------------------------------------------------------------- 系统Prompt缓存大小: 1.00 MB 缓存池状态: 1 项 ----------------------------------------------------------------- 【场景2】请求2-5 - 复用系统Prompt缓存 ----------------------------------------------------------------- 请求2: ✓ 命中 | 提示词: '你是一个智能助手' | 节省: 1.00 MB 请求3: ✓ 命中 | 提示词: '你是一个智能助手' | 节省: 1.00 MB 请求4: ✓ 命中 | 提示词: '你是一个智能助手' | 节省: 1.00 MB 请求5: ✓ 命中 | 提示词: '你是一个智能助手' | 节省: 1.00 MB ----------------------------------------------------------------- 【场景3】用户A/B/C - 不同系统Prompt ----------------------------------------------------------------- 用户A: 新缓存写入 | Prompt: '你是编程助手...' 用户B: 新缓存写入 | Prompt: '你是翻译专家...' 用户C: ✓ 复用缓存 | Prompt: '你是编程助手...' (与用户A相同) ================================================================= 【效果统计】 ================================================================= 缓存池项数: 3 系统Prompt复用次数: 4 次 累计节省显存: 4.00 MB 传统方式需显存: 5.00 MB (5份独立) 共享方式需显存: 1.00 MB (1份共享) 节省比例: 80% ================================================================= 核心价值: N个请求共享1份缓存,显存节省 (N-1)/N =================================================================
技术细节:
5.1 融合优化执行流程
我们将分层缓存、量化压缩、动态淘汰、全局共享四大技术融合,形成端到端KV Cache优化流程,这是应用落地实践长上下文推理的标准方案:

5.2 融合优化显存效果对比
缓存类型 | 1K Token 显存 | 10K Token 显存 | 100K Token 显存 | 支持上下文上限 |
|---|---|---|---|---|
原生 KV Cache | 8GB | 80GB | 800GB | 8K(A100) |
融合优化 KV Cache | 1.2GB | 10GB | 80GB | 100K+(A100) |
5.3 关键技术总结
四大技术相辅相成,共同解决长上下文显存爆炸问题,是大模型推理的核心基础设施。
总的来说,分层缓存、量化压缩、动态淘汰、全局共享四大优化方案,是破解长上下文显存瓶颈的关键。量化从数据精度入手缩小缓存体积,动态淘汰筛选低权重无效Token节省空间,分层缓存贴合Transformer不同层级的特征差异做精细化管控,全局共享则解决批量场景下重复缓存的资源浪费问题,四项技术互补配合,就能在几乎不损耗生成效果的前提下,大幅压低显存占用。
大模型推理优化不是单一技术的堆砌,而是贴合模型结构、硬件限制与业务场景的综合调优。初次接触先吃透注意力机制与KV Cache底层逻辑,再逐个拆解单项优化原理。多对比优化前后的显存、性能差异,慢慢建立工程化思维。后续可以结合长文本推理落地场景深入研究,循序渐进掌握大模型部署优化的核心能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。