LLM高效推理:KV缓存与分页注意力机制深度解析

本文涉及的产品
实时计算 Flink 版,1000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: 随着大型语言模型(LLM)规模和复杂性的增长,高效推理变得至关重要。KV缓存和分页注意力是优化LLM推理的两项关键技术。KV缓存通过存储键值对减少重复计算,而分页注意力则通过将序列分割成小块来降低内存消耗,从而有效处理长序列。本文深入剖析这些技术的工作原理及其在仅解码器模型中的应用,探讨其优势与挑战,并展示其实现示例。

随着大型语言模型(LLM)规模和复杂性的持续增长,高效推理的重要性日益凸显。KV(键值)缓存与分页注意力是两种优化LLM推理的关键技术。本文将深入剖析这些概念,阐述其重要性,并探讨它们在仅解码器(decoder-only)模型中的工作原理。

常规推理机制

首先,我们通过一个简单的例子来理解Transformer模型中典型的推理过程。假设我们需要生成短语:

“The quick brown fox jumped”

以下是常规推理的简化实现:

 import numpy as np
# 简化的嵌入表示,仅用于演示
embeddings = {
    'The': np.array([1, 0, 0, 0]),
    'quick': np.array([0, 1, 0, 0]),
    'brown': np.array([0, 0, 1, 0]),
    'fox': np.array([0, 0, 0, 1]),
    'jumped': np.array([1, 1, 0, 0])
}

# 权重矩阵(简化)
W_Q = W_K = W_V = np.array([[1, 0],
    [0, 1],
    [0, 0],
    [0, 0]])

def compute_attention(self, input_words):
    # 将单词转换为嵌入向量
    E = np.array([embeddings[word] for word in input_words])

    # 计算所有token的K和V矩阵
    K = E @ W_K  # 形状: (seq_len, 2)
    V = E @ W_V  # 形状: (seq_len, 2)

    # 计算最后一个token的Q矩阵
    Q = E[-1] @ W_Q  # 形状: (1, 2)

    # 计算缩放的点积注意力得分
    scale = np.sqrt(2)  # 缩放因子,为key/query维度(此处为2)的平方根
    scores = (Q @ K.T) / scale  # 形状: (1, seq_len)

    # 应用Softmax函数,获得注意力权重
    attention_weights = self.softmax(scores)  # 形状: (1, seq_len)

    # 将注意力权重应用于V矩阵
    output = attention_weights @ V  # 形状: (1, 2)

     return output

以下是逐步生成的过程:

 # 步骤1: 生成 "brown"
input_words_step1 = ['The', 'quick']
output_step1 = compute_attention(input_words_step1)
# 步骤2: 生成 "fox"
input_words_step2 = ['The', 'quick', 'brown']
output_step2 = compute_attention(input_words_step2)
# 步骤3: 生成 "jumped"
input_words_step3 = ['The', 'quick', 'brown', 'fox']
 output_step3 = compute_attention(input_words_step3)

冗余计算:观察上述代码可以发现对于每个新生成的token:

  1. 需要为所有先前的token重新计算K和V矩阵。
  2. 矩阵的大小随着token数量的增加而增大。
  3. 存在大量不必要的重复计算。

KV缓存机制

当使用Transformer模型生成文本时,通过缓存键(K)和值(V)矩阵,可以显著优化推理过程。下图展示了KV缓存的工作原理:

在上图中:

  1. q_new表示最新token的查询向量。
  2. K_prevV_prev是从先前计算中缓存得到的键和值矩阵。
  3. k_newv_new仅为当前新token计算。
  4. 蓝色箭头表示如何利用缓存值和新值计算注意力。

以下是KV缓存的实现示例:

 def compute_attention_with_cache(self, input_words):
    """使用KV缓存计算注意力"""
    # 获取新token(序列中的最后一个单词)
    new_word = input_words[-1]
    e_new = embeddings[new_word]

    # 计算新token的K和V矩阵
    K_new = e_new @ W_K  # 形状: (2,)
    V_new = e_new @ W_V  # 形状: (2,)

    # 更新缓存的K和V矩阵
    if self.cached_K is None:
        self.cached_K = K_new.reshape(1, -1)  # 形状: (1, 2)
        self.cached_V = V_new.reshape(1, -1)  # 形状: (1, 2)
    else:
        self.cached_K = np.vstack([self.cached_K, K_new])  # 形状: (seq_len, 2)
        self.cached_V = np.vstack([self.cached_V, V_new])  # 形状: (seq_len, 2)

    # 计算最后一个token的Q矩阵
    Q = e_new @ W_Q  # 形状: (2,)

    # 使用缓存的K矩阵计算缩放的点积注意力得分
    scale = np.sqrt(2)  # 缩放因子,为key/query维度(此处为2)的平方根
    scores = (Q @ self.cached_K.T) / scale  # 形状: (1, seq_len)

    # 应用Softmax函数,获得注意力权重
    attention_weights = self.softmax(scores)  # 形状: (1, seq_len)

    # 使用缓存的V矩阵计算注意力输出
    output = attention_weights @ self.cached_V  # 形状: (1, 2)

     return output

以下是逐步生成的过程:

 # 步骤1: 生成 "brown"
input_words_step1 = ['The', 'quick']
output_step1 = compute_attention_with_cache(input_words_step1)
# 步骤2: 生成 "fox"
input_words_step2 = ['The', 'quick', 'brown']
output_step2 = compute_attention_with_cache(input_words_step2)
# 步骤 3: 生成 "jumped"
input_words_step3 = ['The', 'quick', 'brown', 'fox']
 output_step3 = compute_attention_with_cache(input_words_step3)

比较有无KV缓存的推理计算

内存需求与挑战

我们来看一个使用典型模型参数的实际例子:

  • 序列长度: 4096
  • 层数: 32
  • 注意力头数: 32
  • 头维度: 128
  • 精度: FP16 (2 bytes)

每个token所需的内存:

 KV_cache_per_token = 2×num_layers×(num_heads×head_dim)×precision
 = 2 × 32 × (32 × 128) × 2 bytes
 = 2 × 32 × 4096 × 2 bytes
 = 524,288 bytes
 ≈ 0.5 MB

KV缓存的低效性

尽管KV缓存显著提高了计算效率,但它也带来了内存管理方面的挑战。以下是三种主要的内存低效类型:

内部碎片

  • 由因未知输出长度而导致的过度分配引起。
  • 示例:在图像中,2040个槽位从未被使用。
  • 影响:可能浪费高达60-80%的已分配内存。
  • 解决方案:更精确的输出长度估计或动态分配策略。

预留浪费

  • 为将来的token生成而预留的内存。
  • 在图像中显示为“3 slots future used (reserved)”。
  • 维持生成连续性的必要措施。
  • 可以通过更好地预测所需的未来槽位来优化。

外部碎片

  • 由处理具有不同序列长度的多个请求导致。
  • 在不同请求之间创建内存间隙。
  • 解决方案包括内存碎片整理和智能请求批处理。

如上图所示,通常仅有20-40%的KV缓存被用于存储实际的token状态。

分页注意力:解决内存低效的方案

为了应对这些内存挑战,可以采用分页注意力机制。

分页注意力是一种用于有效处理Transformer模型中长序列的技术,它通过将注意力计算分解为更小、更易于管理的“页”或“块”来实现。这种方法降低了内存消耗和计算复杂度,从而能够处理原本因过大而无法放入内存的序列。

 def compute_attention_with_paging(self, input_words):
    """使用分页KV缓存计算注意力"""
    # 获取新token(序列中的最后一个单词)
    new_word = input_words[-1]
    e_new = embeddings[new_word]

    # 计算新token的K和V矩阵
    K_new = e_new @ W_K  # 形状: (2,)
    V_new = e_new @ W_V  # 形状: (2,)

    # 确定当前页的索引
    total_tokens = sum(len(K_page) for K_page in self.cached_K_pages) + 1
    current_page_idx = (total_tokens - 1) // PAGE_SIZE

    # 如果需要,初始化新页
    if len(self.cached_K_pages) <= current_page_idx:
        self.cached_K_pages.append([])
        self.cached_V_pages.append([])

    # 将K和V添加到当前页的缓存中
    self.cached_K_pages[current_page_idx].append(K_new)
    self.cached_V_pages[current_page_idx].append(V_new)

    # 计算当前token的Q矩阵
    Q = e_new @ W_Q  # Shape: (2,)

    # 仅在当前页内计算注意力
    K_current_page = np.array(self.cached_K_pages[current_page_idx])
    V_current_page = np.array(self.cached_V_pages[current_page_idx])

    # 添加缩放因子,用于点积注意力
    scale = np.sqrt(2)  # 缩放因子,为key/query维度(此处为2)的平方根
    scores = (Q @ K_current_page.T) / scale

    # 应用Softmax函数,获得注意力权重
    attention_weights = self.softmax(scores)  # 形状: (1, current_page_size)

    # 将注意力权重应用于当前页中的V矩阵
    output = attention_weights @ V_current_page

     return output

以下是逐步生成的过程:

 # 步骤1: 生成 "brown"
input_words_step1 = ['The', 'quick']
output_step1 = compute_attention_with_paging(input_words_step1)
# 步骤2: 生成 "fox"
input_words_step2 = ['The', 'quick', 'brown']
output_step2 = compute_attention_with_paging(input_words_step2)
# 步骤3: 生成 "jumped"
input_words_step3 = ['The', 'quick', 'brown', 'fox']
 output_step3 = compute_attention_with_paging(input_words_step3)

为何需要分页注意力?

  • 内存约束:由于注意力矩阵的规模与序列长度呈平方关系,Transformer模型在处理长序列时面临严重的内存限制。
  • 长序列处理:在诸如语言建模或文档摘要等任务中,序列可能非常长。
  • 效率:通过以分页的方式处理注意力计算,可以将内存使用量保持在一个常量水平,从而不受序列长度的影响。

分页注意力如何工作?

  • 分割序列:将输入序列分割成更小的块或页。
  • 局部注意力:在每个页内计算注意力。
  • 跨页注意力:可选地,允许有限的跨页注意力,以捕获页之间的依赖关系。
  • 滑动窗口:使用重叠的页来确保连续性。

上述实现仅限于局部注意力,跨页注意力和滑动窗口的实现超出了本文的范围,将在后续文章中详细介绍。

分页注意力的讨论

优势

  • 内存效率:注意力计算被限制在页大小内,内存使用量保持恒定,不受总序列长度的影响。
  • 计算效率:降低了注意力计算的复杂度。
  • 可扩展性:能够处理原本无法放入内存的超长序列。

权衡与考虑

  • 上下文信息受限:模型会丢失跨页的一些依赖关系,这对于需要全局上下文的任务可能很重要。

可能的解决方案:

  • 重叠页:允许页之间重叠一定数量的token,重叠区域的token可以关注前一页的token。
  • 分层注意力:使用更高层次的注意力机制来连接跨页的信息。

重叠页、分层注意力、跨页注意力和滑动窗口的完整实现超出了本文的范围。

以下实现仅捕获局部注意力,作为示例不应在实际应用中使用:

 # 本实现仅为演示和理解目的而设计的简化版本。
# 实际应用中需要更高效和可扩展的实现。

import numpy as np

embeddings = {
    'The': np.array([1, 0, 0, 0]),
    'quick': np.array([0, 1, 0, 0]),
    'brown': np.array([0, 0, 1, 0]),
    'fox': np.array([0, 0, 0, 1]),
    'jumped': np.array([1, 1, 0, 0])
}

W_Q = W_K = W_V = np.array([[1, 0],
                            [0, 1],
                            [0, 0],
                            [0, 0]])

PAGE_SIZE = 2  # 演示用的小页尺寸

class AttentionWithCache:
    def __init__(self):
        self.cached_K = None  # 形状: (seq_len, 2)
        self.cached_V = None  # 形状: (seq_len, 2)
        self.cached_K_pages = []  # 包含K向量的页列表
        self.cached_V_pages = []  # 包含V向量的页列表

    def softmax(self, x, axis=-1):
        """
        为x中的每组分数计算Softmax值。
        包含数值稳定性改进。
        """
        # 应用最大值减法以提高数值稳定性
        x_max = np.max(x, axis=axis, keepdims=True)
        exp_x = np.exp(x - x_max)
        return exp_x / np.sum(exp_x, axis=axis, keepdims=True)

    def compute_attention(self, input_words):
        # 将单词转换为嵌入向量
        E = np.array([embeddings[word] for word in input_words])

        # 计算所有token的K和V矩阵
        K = E @ W_K  # 形状: (seq_len, 2)
        V = E @ W_V  # 形状: (seq_len, 2)

        # 计算最后一个token的Q矩阵
        Q = E[-1] @ W_Q  # 形状: (1, 2)

        # 计算缩放的点积注意力得分
        scale = np.sqrt(2)  # 缩放因子,为key/query维度(此处为2)的平方根
        scores = (Q @ K.T) / scale  # 形状: (1, seq_len)

        # 应用Softmax函数,获得注意力权重
        attention_weights = self.softmax(scores)  # 形状: (1, seq_len)

        # 将注意力权重应用于V矩阵
        output = attention_weights @ V  # 形状: (1, 2)

        return output

    def compute_attention_with_cache(self, input_words):
        """使用KV缓存计算注意力"""
        # 获取新token(序列中的最后一个单词)
        new_word = input_words[-1]
        e_new = embeddings[new_word]

        # 计算新token的K和V矩阵
        K_new = e_new @ W_K  # 形状: (2,)
        V_new = e_new @ W_V  # 形状: (2,)

        # 更新缓存的K和V矩阵
        if self.cached_K is None:
            self.cached_K = K_new.reshape(1, -1)  # 形状: (1, 2)
            self.cached_V = V_new.reshape(1, -1)  # 形状: (1, 2)
        else:
            self.cached_K = np.vstack([self.cached_K, K_new])  # 形状: (seq_len, 2)
            self.cached_V = np.vstack([self.cached_V, V_new])  # 形状: (seq_len, 2)

        # 计算最后一个token的Q矩阵
        Q = e_new @ W_Q  # 形状: (2,)

        # 使用缓存的K矩阵计算缩放的点积注意力得分
        scale = np.sqrt(2)  # 缩放因子,为key/query维度(此处为2)的平方根
        scores = (Q @ self.cached_K.T) / scale  # 形状: (1, seq_len)

        # 应用Softmax函数,获得注意力权重
        attention_weights = self.softmax(scores)  # 形状: (1, seq_len)

        # 使用缓存的V矩阵计算注意力输出
        output = attention_weights @ self.cached_V  # 形状: (1, 2)

        return output

    def compute_attention_with_paging(self, input_words):
        """使用分页KV缓存计算注意力"""
        # 获取新token(序列中的最后一个单词)
        new_word = input_words[-1]
        e_new = embeddings[new_word]

        # 计算新token的K和V矩阵
        K_new = e_new @ W_K  # 形状: (2,)
        V_new = e_new @ W_V  # 形状: (2,)

        # 确定当前页的索引
        total_tokens = sum(len(K_page) for K_page in self.cached_K_pages) + 1
        current_page_idx = (total_tokens - 1) // PAGE_SIZE

        # 如果需要,初始化新页
        if len(self.cached_K_pages) <= current_page_idx:
            self.cached_K_pages.append([])
            self.cached_V_pages.append([])

        # 将K和V添加到当前页的缓存中
        self.cached_K_pages[current_page_idx].append(K_new)
        self.cached_V_pages[current_page_idx].append(V_new)

        # 计算当前token的Q矩阵
        Q = e_new @ W_Q  # Shape: (2,)

        # 仅在当前页内计算注意力
        K_current_page = np.array(self.cached_K_pages[current_page_idx])
        V_current_page = np.array(self.cached_V_pages[current_page_idx])

        # 添加缩放因子,用于点积注意力
        scale = np.sqrt(2)  # 缩放因子,为key/query维度(此处为2)的平方根
        scores = (Q @ K_current_page.T) / scale

        # 应用Softmax函数,获得注意力权重
        attention_weights = self.softmax(scores)  # 形状: (1, current_page_size)

        # 将注意力权重应用于当前页中的V矩阵
        output = attention_weights @ V_current_page

        return output

def compare_implementations():
    print("原始实现:")
    attention1 = AttentionWithCache()

    # 使用原始方法处理序列
    for i in range(len(['The', 'quick', 'brown', 'fox'])):
        words = ['The', 'quick', 'brown', 'fox'][:i + 1]
        output = attention1.compute_attention(words)
        print(f"处理 {words} 后的输出:")
        print(f"Output: {output}")

    print("\nKV缓存实现:")
    attention2 = AttentionWithCache()

    # 使用KV缓存处理序列
    for i in range(len(['The', 'quick', 'brown', 'fox'])):
        words = ['The', 'quick', 'brown', 'fox'][:i + 1]
        output = attention2.compute_attention_with_cache(words)
        print(f"处理 {words} 后的输出:")
        print(f"Output: {output}")

    print("\n分页注意力实现:")
    attention3 = AttentionWithCache()

    # 使用分页注意力处理序列
    for i in range(len(['The', 'quick', 'brown', 'fox'])):
        words = ['The', 'quick', 'brown', 'fox'][:i + 1]
        output = attention3.compute_attention_with_paging(words)
        print(f"处理 {words} 后的输出:")
        print(f"Output: {output}")
        print(f"页数: {len(attention3.cached_K_pages)}")
        print(f"当前页大小: {len(attention3.cached_K_pages[-1])}\n")

if __name__ == "__main__":
     compare_implementations()

总结

KV缓存和分页注意力是提升LLM推理效率和可扩展性的重要技术。KV缓存通过消除冗余计算来优化计算过程,而分页注意力则解决了处理长序列时面临的内存限制。

随着模型规模和复杂性的不断增长,这些优化技术对于实际应用变得至关重要。深入理解和有效实施这些技术,可以显著提升LLM部署的性能和效率。

https://avoid.overfit.cn/post/db8875e43cd245359577a52c6018f81a

作者:Dewang Sultania

目录
相关文章
|
2月前
|
存储 机器学习/深度学习 算法
​​LLM推理效率的范式转移:FlashAttention与PagedAttention正在重塑AI部署的未来​
本文深度解析FlashAttention与PagedAttention两大LLM推理优化技术:前者通过分块计算提升注意力效率,后者借助分页管理降低KV Cache内存开销。二者分别从计算与内存维度突破性能瓶颈,显著提升大模型推理速度与吞吐量,是当前高效LLM系统的核心基石。建议收藏细读。
623 125
|
1月前
|
人工智能 自然语言处理 TensorFlow
134_边缘推理:TensorFlow Lite - 优化移动端LLM部署技术详解与实战指南
在人工智能与移动计算深度融合的今天,将大语言模型(LLM)部署到移动端和边缘设备已成为行业发展的重要趋势。TensorFlow Lite作为专为移动和嵌入式设备优化的轻量级推理框架,为开发者提供了将复杂AI模型转换为高效、低功耗边缘计算解决方案的强大工具。随着移动设备硬件性能的不断提升和模型压缩技术的快速发展,2025年的移动端LLM部署已不再是遥远的愿景,而是正在成为现实的技术实践。
|
2月前
|
机器学习/深度学习 人工智能 前端开发
解决推理能力瓶颈,用因果推理提升LLM智能决策
从ChatGPT到AI智能体,标志着AI从对话走向自主执行复杂任务的能力跃迁。AI智能体可完成销售、旅行规划、外卖点餐等多场景任务,但其发展受限于大语言模型(LLM)的推理能力。LLM依赖统计相关性,缺乏对因果关系的理解,导致在非确定性任务中表现不佳。结合因果推理与内省机制,有望突破当前AI智能体的推理瓶颈,提升其决策准确性与自主性。
295 6
解决推理能力瓶颈,用因果推理提升LLM智能决策
|
1月前
|
存储 监控 安全
132_API部署:FastAPI与现代安全架构深度解析与LLM服务化最佳实践
在大语言模型(LLM)部署的最后一公里,API接口的设计与安全性直接决定了模型服务的可用性、稳定性与用户信任度。随着2025年LLM应用的爆炸式增长,如何构建高性能、高安全性的REST API成为开发者面临的核心挑战。FastAPI作为Python生态中最受青睐的Web框架之一,凭借其卓越的性能、强大的类型安全支持和完善的文档生成能力,已成为LLM服务化部署的首选方案。
|
1月前
|
机器学习/深度学习 缓存 PyTorch
131_推理加速:ONNX与TensorRT深度技术解析与LLM模型转换优化实践
在大语言模型(LLM)时代,高效的推理加速已成为部署高性能AI应用的关键挑战。随着模型规模的不断扩大(从BERT的数亿参数到GPT-4的数千亿参数),推理过程的计算成本和延迟问题日益突出。ONNX(开放神经网络交换格式)和TensorRT作为业界领先的推理优化框架,为LLM的高效部署提供了强大的技术支持。本文将深入探讨LLM推理加速的核心原理,详细讲解PyTorch模型转换为ONNX和TensorRT的完整流程,并结合2025年最新优化技术,提供可落地的代码实现与性能调优方案。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
37_开源LLM:LLaMA与Mistral的突破_深度解析
在人工智能领域,2025年已经成为开源大语言模型的黄金时代。从Meta的LLaMA系列到欧洲初创公司Mistral AI的创新突破,开源LLM正在重塑整个AI生态系统的格局。截至2025年4月,Meta的LLaMA系列已成为全球下载量最高、社区使用最活跃的开源大语言模型之一,并被集成于数百个学术项目、创业平台和AI产品之中
|
1月前
|
缓存 监控 安全
80_离线环境搭建:无互联网LLM推理
在当今大语言模型(LLM)蓬勃发展的时代,许多组织和个人面临着一个共同的挑战:如何在无互联网连接的环境中高效部署和使用LLM?这一需求源于多方面的考量,包括数据安全、隐私保护、网络限制、极端环境作业等。2025年,随着企业对数据主权意识的增强和边缘计算的普及,离线LLM部署已成为AI应用落地的关键场景之一。
|
1月前
|
存储 机器学习/深度学习 人工智能
46_LLM幻觉问题:来源与早期研究_深度解析
大型语言模型(LLM)在自然语言处理领域展现出了令人惊叹的能力,能够生成连贯的文本、回答复杂问题、进行创意写作,甚至在某些专业领域提供见解。然而,这些强大模型的一个根本性缺陷——幻觉问题,正成为限制其在关键应用中广泛部署的主要障碍。幻觉(Hallucination)指的是LLM生成的内容与事实不符、上下文矛盾、逻辑错误,或者完全虚构信息的现象。
|
2月前
|
存储 缓存 负载均衡
LLM推理成本直降60%:PD分离在大模型商业化中的关键价值
在LLM推理中,Prefill(计算密集)与Decode(访存密集)阶段特性不同,分离计算可提升资源利用率。本文详解vLLM框架中的PD分离实现及局限,并分析Dynamo、Mooncake、SGLang等主流方案,探讨KV缓存、传输机制与调度策略,助力LLM推理优化。建议点赞收藏,便于后续查阅。
1482 1
|
4月前
|
人工智能 自然语言处理 API
AI-Compass LLM推理框架+部署生态:整合vLLM、SGLang、LMDeploy等顶级加速框架,涵盖本地到云端全场景部署
AI-Compass LLM推理框架+部署生态:整合vLLM、SGLang、LMDeploy等顶级加速框架,涵盖本地到云端全场景部署
AI-Compass LLM推理框架+部署生态:整合vLLM、SGLang、LMDeploy等顶级加速框架,涵盖本地到云端全场景部署

热门文章

最新文章

推荐镜像

更多
  • DNS