在数字化浪潮的今天,人工智能技术正以前所未有的速度改变着我们的生活和工作方式,其中大语言模型的崛起尤为引人瞩目。

从智能对话机器人到文本创作助手,从数据智能分析到工艺优化、工程设计辅助等,这些模型凭借强大的语言理解和生成能力,为我们带来了诸多惊喜。然而,随着模型规模的不断扩大,如何高效地进行推理运算,成为了摆在研究人员面前的一道难题。

以DeepSeek-R1为例,在处理复杂的多轮对话和长文本生成任务时,其庞大的参数规模和计算量对推理效率提出了极高的要求。

而KVCache技术,正是解决这一难题的关键所在。一般技术类文章都是结合英伟达的GPU架构去介绍KVCache,本文是从NPU的视角,尤其是国产算力芯片标杆,华为的昇腾的架构和实现去阐述如何工程应用这一技术。
1

什么是KVCache?KVCache有什么用?

KVCache(键值缓存)是⼀种⽤于加速Transformer模型⽣成⽂本(当我们提到⽂本时,实际指的是Token)的技术。简单来说,它就像“数学考试时记住前⾯步骤的结果”,避免重复计算。从字⾯上看,KVCache 和 Cache很相似,似乎都是数据缓存的意思,但在 LLM 的上下文下,两者还是有着本质的区别的。

  1. KVCache是什么?

当Transformer模型逐字(Token)⽣成答案(⽐如写故事)时,每次新⽣成⼀个字都需要参考之前所有字的信息。KVCache会把之前计算过的关键数据(Key和Value)保存下来,这样就不⽤每次都从头算⼀遍,⼤幅提⾼速度。

它专⻔⽤于Transformer模型在⽣成⽂本任务的解码(逐个⽣成⽂本)阶段(⽐如GPT这种模型),因为这些模型⽣成⽂字时只能看到前⾯的内容,后⾯的内容被“遮挡”(mask)了,所以可以放⼼缓存前⾯的结果。

  1. KVCache不是什么?

不是数据库缓存:数据库缓存是临时存储⽤户查询的热⻔数据(⽐如⽹站⻚⾯),⽽KVCache缓存的是⼤语⾔模型计算过程的中间结果,与具体任务⽆关。

不是内存缓存:操作系统的内存缓存是加快⽂件读取速度(⽐如常⽤软件启动更快),⽽KVCache是模型算法的优化,专为减少重复数学计算设计。

⽐如,你写作⽂时如果每写⼀句都要从头读⼀遍之前的内容,速度会很慢。⽤KVCache就相当于把每句话的重点先记下来,后⾯直接⽤笔记,不⽤重读整篇作⽂。
那么Transformer 模型架构中为啥需要 KVCache呢?Transformer 使⽤ KVCache 主要是为了在⽣成⽂本(Token)时⼤幅提升效率,原因可以分为以下三点:

(1)避免重复计算

当模型逐字⽣成回答(⽐如写故事或对话)时,每次新⽣成⼀个字都需要重新计算之前所有字的信息。如果不缓存,每次都要从头计算所有字的 Key 和 Value(类似于数学公式中的关键参数),这会浪费⼤量时间和计算资源。KVCache 通过保存这些中间结果,让后续⽣成时直接复⽤,省去重复运算。

(2)Decoder 架构的天然适配

Transformer 的 Decoder 模型(如 GPT)⽣成⽂本时,只能看到前⾯的内容,后⾯的内容被“遮挡”(Causal Mask),因此每次只需关注已⽣成的部分。这种特性使得之前计算的 Key 和 Value 可以被安全缓存,不会被后续新⽣成的字符影响。例如,⽣成第 5 个字时,直接⽤前 4 个字的缓存结果即可,不需要重新计算。

(3)空间换时间,降低计算成本

KVCache 本质是⽤内存空间换取更快的速度。虽然缓存会占⽤显存,但相⽐重复计算带来的时间消耗,这种取舍能显著提升⽣成效率,尤其是⽣成⻓⽂本时效果更明显。例如,⽣成 100 个字的⽂本,若不缓存可能需要计算 100² 次,⽽缓存后仅需线性增⻓的计算量。对⽐来说:

— 不是通⽤缓存(如数据库缓存):KVCache 专⻔针对 Transformer 的⾃回归⽣成机制设计,缓存的是中间计算结果,⽽⾮⽤户数据或⽂件。

— 不⽤于训练阶段:训练时可以并⾏处理整个⽂本,⽆需逐字⽣成,因此不需要缓存。

简单理解,就像写作业时跳过重复的验算步骤,直接使⽤之前的结果,既能加快速度,⼜能减少精⼒消耗。

2

KVCache与大模型推理的关系?

KVCache 与⼤语⾔模型推理系统的关系可以从以下四⽅⾯理解:

  1. 加速⽣成效率的核⼼技术

KVCache 通过缓存⾃注意⼒机制中的 Key(键)和 Value(值),避免⽣成每个新 Token 时重复计算历史信息。例如,⽣成第 N 个 Token 时,仅需计算当前 Token 对应的 Query(查询),⽽ Key 和 Value 直接复⽤之前缓存的结果,显著减少计算量。这种优化尤其适⽤于⼤语⾔模型逐字⽣成⽂本的场景(如对话或续写),使推理速度成倍提升。

  1. 缓解内存瓶颈的关键⼿段

⼤语⾔模型的推理分为 Prefill(预填充) 和 Decoding(解码) 两个阶段:

— Prefill 阶段:处理全部输⼊ Prompt,计算密集但⽆需缓存;

— Decoding 阶段:⾃回归⽣成 Token,内存需求激增。

KVCache 在 Decoding 阶段动态存储中间状态,通过空间换时间的策略优化显存使⽤,避免因⻓序列⽣成导致的内存溢出问题。例如,⽣成⻓⽂本时,若未使⽤缓存,显存占⽤会随序列⻓度平⽅增⻓,⽽缓存后仅线性增⻓。

  1. 平衡效率与模型表现的调控参数

KVCache 的缓存⼤⼩直接影响推理效果和速度:

— 缓存过⼩:可能丢弃部分历史信息,导致⽣成内容连贯性下降(如实验显示缓存不⾜时 PPL 指标恶化,模型表现变差);
— 缓存过⼤:占⽤过多显存,限制⽣成⻓度或并发能⼒。
因此,推理系统需根据硬件条件和任务需求动态调整缓存策略,实现效率与质量的平衡。

  1. 推理系统设计的底层⽀撑

KVCache 管理(如缓存分配、更新和淘汰机制)是推理系统优化的核⼼⽅向之⼀。例如:

— 流式⽣成场景:需设计滑动窗⼝缓存,保留近期关键信息并丢弃冗余数据;

— 批处理推理:需并⾏管理多组缓存以⽀持多任务同时⽣成。

这些设计直接影响系统的吞吐量、延迟和资源利⽤率。

总的来说,KVCache 不是独⽴的组件,⽽是深度嵌⼊推理系统的优化⼿段。它通过减少计算冗余、控制内存消耗,成为⼤语⾔模型⾼效⽣成的核⼼技术,同时其配置管理也是系统级调优的重要维度。

3

KVCache的大小如何计算?

KVCache 的⼤⼩计算需综合考虑模型结构、序列⻓度及硬件特性,具体⽅法如下:

  1. 基础计算公式

KVCache 的总⼤⼩主要由以下参数决定:

— 模型层数(L):Transformer 的层数,每层需独⽴缓存 K 和 V。

— 注意⼒头数(H) 与 单头维度(d):每个头的 K/V 向量维度为 d,单层单 Token 的 KV 缓存量为 2 * H * d(K 和 V 各占⼀份)。

— 序列⻓度(S):已⽣成 Token 的数量,缓存量与 S 成正⽐。

— 数据类型⼤⼩(B):如 float16 占 2 字节,int8 占 1 字节。

— 公式:
总缓存⼤⼩ = L × S × 2 × H × d × B

示例:

若模型为 32 层(L=32),每层 16 头(H=16),单头维度 128(d=128),⽣成序列⻓度 1024(S=1024),使⽤ float16(B=2),则总缓存为:

32 × 1024 × 2 × 16 × 128 × 2 = 268,435,456 字节 ≈ 256 MB

(注:此示例参数需根据具体模型调整)具体的代码和计算逻辑,请参考第四节。

  1. 关键影响因素与优化⽅向

— 序列⻓度(S):

⽣成⻓⽂本时,S 线性增⻓导致缓存膨胀,需通过 窗⼝裁剪 或 动态淘汰策略 限制 S 上限。

例如,限制 S ≤ 4096,超出部分丢弃早期 Token 的缓存。

— 模型结构:

若模型隐藏层维度 D 远⼤于单头维度 d(常⻅设计),KVCache 占⽤可控;反之需特殊优化(如分组缓存)。

— 硬件显存限制:

根据显存容量反推最⼤⽀持的 S。例如 24GB 显存的 GPU,扣除模型参数占⽤后,剩余显存需容纳 L × S × 2 × H × d × B。

  1. 实际配置步骤

(1)获取模型参数:

从模型配置⽂件中提取 L、H、d(如 LLaMA-7B:L=32, H=32, d=128)。

(2)设定序列⻓度上限:

根据任务需求(如对话场景 S=2048)和显存余量(显存余量 ≥ L × S × 2 × H × d × B)确定 S。

(3)数据类型选择:

优先使⽤低精度(如 float16 或量化⾄ int8),可减少 B 值。

(4)动态调整:

流式⽣成场景中,采⽤滑动窗⼝机制,仅保留最近 N 个 Token 的缓存(如 N=512)。

  1. 算⼒平台适配示例

假设在 NVIDIA A100 (40GB) 上运⾏ LLaMA-13B:

模型参数:L=40, H=40, d=128,float16(B=2)。

模型参数显存占⽤约 26GB,剩余显存约 14GB。

可⽀持的最⼤ S 计算:

14GB ≈ 40 × S × 2 × 40 × 128 × 2 → S ≈ 14×10^9 / (40×2×40×128×2) ≈ 1700

实际配置时需预留缓冲,通常设置 S=1536 或 2048。

总结:KVCache ⼤⼩由模型结构、序列⻓度和数据类型共同决定,需结合算⼒平台显存限制动态配置,通过公式计算与策略优化实现效率平衡。
4

NPU中如何使用KVCache?

前⾯介绍了 KVCache 这个优化推理性能的机制。下⾯结合昇腾 NPU 的 ATB(Ascend TransformerBoost) 组件分析 KVCache 在推理过程中的作⽤。

  1. KVCache 的分配和初始化

基于 ATB 组件启动离线推理,需要预先分配推理过程中需要的 KVCache。分配多⼤的KVCache 有两种策略:

(1)基于模型的序列⻓度和 Batch Size,预分配 KVCache:序列⻓度取决于:

    ① 输⼊序列⻓度(即⽤户输⼊的 prompt 经过 tokenizer 分词之后的 token 个数);

    ② 输出序列⻓度(模型⾃回归⽣成的 Token 的个数)。两者的总和不能超过模型的窗⼝⼤⼩。另外,输出的⻓度⼀般和模型训练时的语料⻓度分布有关系,⼀般不会超过输⼊的⻓ 度。在实际的推理性能压测时,⼀般设置为1:1。即128K 的窗⼝下,输⼊输出都设置为64K。

(2)基于 NPU 卡现有显存的⼤⼩,预分配 KVCache:

通过直接配置NPU_MEMORY_FRACTION 参数可以设置显存使⽤的⽐率。在确定KVCache 的分配策略之后,下⾯结合代码看下 KVCache 是如何实际分的。

class CacheManager:

def __init__(self, cache_config, model_config):
    self.block_size = cache_config.block_size
    self.num_blocks = cache_config.num_blocks
    self.num_heads = model_config.num_kv_heads
    self.head_size = model_config.head_size
    self.num_layers = model_config.num_layers
    self.device = model_config.device
    self.dtype = model_config.dtype
    self.soc_info = model_config.soc_info
    
    mem_need = self.num_blocks * self.block_size * self.num_heads * self.head_size * self.num_layers * 2 * \
               self.get_dtype_size(self.dtype) / 1024 / 1024 / 1024
    logger.info(f"kv cache will allocate {mem_need}GB memory")

如果硬件平台需要 NZ 填充,则以特定形状初始化缓存。

    if self.soc_info.need_nz:
        self.kv_cache = [
            (
                torch.empty(
                    (self.num_blocks, self.num_heads * self.head_size // 16, self.block_size, 16),
                    dtype=self.dtype,
                    device=self.device,
                ),
                torch.empty(
                    (self.num_blocks, self.num_heads * self.head_size // 16, self.block_size, 16),
                    dtype=self.dtype,
                    device=self.device,
                ),
            )
            for _ in range(self.num_layers)
        ]
    else: # 否则以常规的 ND 形状填充。
        self.kv_cache = [
            (
                torch.empty(
                    (self.num_blocks, self.block_size, self.num_heads, self.head_size),
                    dtype=self.dtype,
                    device=self.device,
                ),
                torch.empty(
                    (self.num_blocks, self.block_size, self.num_heads, self.head_size),
                    dtype=self.dtype,
                    device=self.device,
                ),
            )
            for _ in range(self.num_layers)
        ]
 ......

那么上⾯代码⾥的两种 KVCache 初始化的⽅法有什么区别呢?

在代码中,NZ 填充和 ND 填充是指在创建缓存KVCache时,根据硬件平台的需求和特性,以不同的⽅式初始化缓存张量的形状和内容。在 NPU卡上,910是常规的ND数据排布, 310则使⽤了NZ分形数据排布,KVCache分配时需要考虑。以下是详细的解释:

NZ填充(need_nz 为 True 时)

当硬件平台需要 NZ 填充时,缓存的初始化会按照特定的形状(⼤ N ⼩ Z 分形)进⾏,以满⾜硬件对数据对⻬或计算效率的要求。

具体来说,代码中会将键(Key)和值(Value)的缓存张量初始化为以下形状:
(self.num_blocks, self.num_heads * self.head_size // 16, self.block_size, 16)

这种初始化⽅式确保了数据在硬件上的⾼效存储和计算,避免了因数据对⻬问题导致的性能下降。
ND填充(need_nz 为 False 时)
当硬件平台不需要 NZ 填充时,缓存的初始化会按照更常规的形状进⾏,直接根据模型的配置来初始化缓存张量。
具体来说,代码中会将键(Key)和值(Value)的缓存张量初始化为以下形状:
(self.num_blocks, self.block_size, self.num_heads, self.head_size)

— self.num_blocks:缓存块的总数。self.block_size:每个缓存块的⼤⼩。
— self.num_heads:注意⼒机制中的头数。
— self.head_size:每个注意⼒头的维度⼤⼩。
这种初始化⽅式更直接地反映了模型的逻辑结构,便于理解和调试。

NZ 和 ND 两者的区别

— 形状不同:

NZ 填充的缓存张量形状为 (num_blocks, num_heads * head_size // 16, block_size, 16)。

ND 填充的缓存张量形状为 (num_blocks, block_size, num_heads, head_size)。

— 适用场景不同:

NZ 填充适用于需要满足硬件特定数据对齐要求的场景,以提高计算效率。

ND 填充适用于一般的模型训练和推理场景,更符合模型的逻辑结构。

— 数据存储方式不同:

NZ 填充可能会引入额外的填充数据,以确保数据对齐。

ND 填充则严格按照模型的实际需求存储数据,没有额外的填充。

总的来说,NZ填充和ND填充的选择取决于具体的硬件平台和模型需求。NZ填充在某些硬件上(⽐如 Ascend 310)可以提⾼性能,⽽常规的 ND 则更通⽤且易于理解。通过在代码中根据 soc_info.need_nz 的值来选择不同的初始化⽅式,可以灵活地适配不同的硬件环境。ND 和 NZ 格式更多的信息可以参考最后一节。

  1. KVCache 的计算逻辑和使⽤场景

从 FlashAttention 算⼦和 KVCache 算⼦的⻆度,我们看⼀下 KVCache 在 Transformers ⾃回归模型中,是如何计算和更新的。

—⾃注意⼒机制—

图片

—KVCache 的计算逻辑—
图片
prefix_ntokens = 0

for i in range(batch):
for j in range(seqlen[i]):
cache_out[layer_id[0]][i][token_offset[i] - seqlen[i] + j][:] =
newkv[prefix_ntokens + j][:]
# 只会修改layer_id表示的layer的cache内容
prefix_ntokens += seqlen[i]

变量说明

— prefix_ntokens:用于记录当前处理到的总token数,初始值为0。

— batch:批次大小,表示一次处理的样本(Request)数量。

— seqlen:一个数组,表示每个样本(Request)的序列长度。

— layer_id:一个数组,表示当前处理的层的ID。

— cache_out:KVCache 输出,是一个多维数组,用于存储 KVCache 数据。

— token_offset:一个数组,表示每个样本(Request)在 KVCache 中的起始位置。

— newkv:新的键值对数据,是一个二维数组

KVCache 的使⽤场景⽤于transformer推理阶段。
5

KVCache的前沿研究
KVCache 的前沿热点研究主要围绕效率优化与扩展性突破展开,重点⽅向如下:

  1. 分布式 KVCache 架构

分离式推理集群设计:将预填充(Prefill)与解码(Decoding)集群独⽴部署,避免资源竞争,同时构建独⽴的 KVCache 缓存池,提升⻓上下⽂任务的处理效率。例如,Mooncake 系统通过分离架构,⽀持动态分配计算资源(如 CPU、GPU、SSD),显著提⾼硬件利⽤率。跨节点缓存共享:通过 RDMA ⾼速⽹络实现多节点间的 KVCache 同步,降低⻓序列⽣成时的单节点内存压⼒。

  1. ⾃适应缓存调度策略

— 动态上下⽂适配:根据输⼊序列⻓度⾃动调整 KVCache 存储策略,短上下⽂场景减少缓存冗余,⻓上下⽂场景启⽤分层存储(⾼频数据存内存,低频数据存 SSD)。

— 优先级淘汰机制:基于注意⼒权重或 Token 位置动态淘汰低价值缓存(如早期⽣僻Token),缓解显存压⼒。

  1. 压缩与分层存储技术

— 量化与稀疏化:对 KVCache 进⾏低精度量化(如 FP16→INT8)或结构化裁剪,减少单Token 存储开销。

— SSD-内存混合存储:将活跃度低的 KVCache 卸载⾄⾼速 SSD,利⽤内存缓存⾼频访问数据,平衡成本与性能。华为在 Mooncake 相关合作中已验证此⽅案的可⾏性。

  1. 异构资源协同优化

— CPU-GPU 联合调度:利⽤ CPU 内存扩展 KVCache 容量,通过零拷⻉技术减少 GPU 显存占⽤,⽀持超⻓⽂本⽣成(如 100K Tokens 以上)。

— ⽹络与存储协同:优化 NVMe 协议与⽹络传输,加速跨设备缓存访问,降低分布式场景下的延迟。

  1. 开源⽣态与标准化推进

— 组件模块化开源:分阶段公开 KVCache 管理核⼼组件(如动态分配算法、压缩⼯具链),促进技术迭代。例如,Mooncake 计划逐步开源其缓存池实现。

— 接⼝标准化:定义通⽤ KVCache API,⽀持多框架(如 PyTorch、JAX)⽆缝接⼊,降低开发者适配成本。

总的来说,当前研究聚焦于 分布式扩展性、动态资源效率 与 存储成本优化,Mooncake 等系统的实践验证了以 KVCache 为中⼼的架构⾰新潜⼒。未来⽅向可能进⼀步融合硬件特性(如存算⼀体)与算法轻量化。
6

KVCache的前沿研究

讲了这么多 KVCache 在 NPU 架构上的原理和⽤法。那么业界有哪些推理引擎服务⽀持 NPU 上的推理应⽤呢?以下列出⼀些,供⼤家参考。

  1. MindIE-Service

mindie-service 是华为昇腾官⽅原⽣⽀持的⼤模型推理服务。底层使⽤了 ATB 加速库作为推理引擎的底座(也可以选择 MindSpore 作为引擎)。上层是华为⾃研的 MindIE-Service 服务框架,也⽀持 TGI 和 vllm(均为 MindIE 适配的私有分⽀,对开源代码改动较⼤)。

图片
2. vllm-mindspore

该项⽬在 gitee 开源,是华为昇思为 vllm开发的推理后端插件,将 mindspore 作为插件和推理引擎后端集成到 vllm 中。仅需⼀个 import 就可以兼容 vllm 的⽣态:

开源地址在: https://gitee.com/mindspore/vllm-mindspore

import vllm_mindspore # Add this line on the top of script.

from vllm import LLM, SamplingParams

Sample prompts.

prompts = [
“I am”,
“Today is”,
“What is”
]

Create a sampling params object.

sampling_params = SamplingParams(temperature=0.0, top_p=0.95)

Create an LLM.

llm = LLM(model=“Qwen/Qwen2.5-32B-Instruct”, tensor_parallel_size=8)

Generate texts from the prompts. The output is a list of RequestOutput objects

that contain the prompt, generated text, and other information.

outputs = llm.generate(prompts, sampling_params)

Print the outputs.

for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

  1. vllm-ascend

最近,华为昇腾与最流⾏的 vllm 开源社区展开了合作,推出了基于 NPU 的 vllm 分⽀版本。这个分⽀版本的⽬标是通过“硬件可插拔的接⼝”,使得 Ascend NPU 后端与 vllm 解耦合。同时,通过vLLM Ascend 插件,当前流⾏的模型架构,包括类 Transformer的、MoE 架构的、 Embedding 的,以及多模态模型都可以⽆缝运⾏在 NPU 上。

图片
开源地址在:https://github.com/vllm-project/vllm-ascend

  1. KsanaLLM

腾讯开源的兼容NVIDIA GPU 和华为昇腾⽣态的⾼性能推理框架,主要使⽤ C++开发,提供了 Python 安装包。主打⾼性能和⾼吞吐,借鉴了 vLLM, TensorRT-LLM, FastTransformer 项⽬中优化过的 CUDA Kernel 算⼦。为了⾼效管理 KVCache,实现了⾃⼰的 PagedAttention 算⼦。NPU ⽅⾯,依赖昇腾的 CANN 组件和 ATB 加速库,并实现了 KVCache 接⼝的转换和对接。另外,还提供了插件机制,可以借此实现⽤户⾃定义的推理前处理和后处理逻辑。

github 地址在:https://github.com/pcg-mlp/KsanaLLM

  1. Deepexi Generation Inference

DGI 是北京滴普科技开发的兼容NVIDIA GPU和华为昇腾NPU⽣态的⾼性能推理框架。主要使⽤ Python 开发,在 KVCache 管理⽅⾯,以及推理 Prefill,Decoding 操作调度⽅⾯实现了优化的调度算法。采⽤推理前后端分离架构,在⾼可⽤、易⽤性和可扩展性⽅⾯进⾏了增强。下一章节会就 DGI 的架构和特性展开论述。
7

参考文档

— MindIE 官⽅⽂档:

https://www.hiascend.com/document/detail/zh/mindie/100/whatismindie/mindie_what_0001.html

— 矩阵乘法基础:

https://www.hiascend.com/doc_center/source/zh/CANNCommunityEdition/80RC1alpha002/devguide/opdevg/ascendcopdevg/atlas_ascendc_10_0060.html

— KVCache与⼀念KVCache之间的转换关系

https://github.com/pcg-mlp/KsanaLLM/blob/main/docs/technology/design/kvcacherelationship-between-ascend-atb-and-ksana-cn.md

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐