一文通透DeepSeek-V2(改造Transformer的中文模型):从DeepSeek LLM到DeepSeek-V2的MLA与MoE

2024-08-26 23:28

本文主要是介绍一文通透DeepSeek-V2(改造Transformer的中文模型):从DeepSeek LLM到DeepSeek-V2的MLA与MoE,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

成就本文有以下三个因素

  1. 24年5.17日,我在我司一课程「大模型与多模态论文100篇」里问道:大家希望我们还讲哪些论文
    一学员朋友小栗说:幻方发布的deepseek-v2
  2. 24年5.24日,我司一课程「大模型项目开发线上营1」里的一学员朋友问我:校长最近开始搞deepseek了吗?刚看了论文,没搞懂MLA那块的cache是怎么算的,我总觉得他的效果应该类似MQA才对,但是反馈是挺好的

    我当时回复他道:目前团队项目上的事情太多,然后近期在写那个KAN
    确实还没来得及看这个deepseek,我近期看下
  3. 我们在继英文层面的论文翻译、审稿、对话、idea提炼之后
    打算再整一下中文层面的「硕士论文修订助手(不限学科,CS和非CS的都涵盖)」
    对于模型的选择,项目组有同事提议DeepSeek,故准备基于这个DeepSeek搞下

而搞之前——近几天,会先写一下它的论文解读,故本文就来了

且一如既往做到,对于几乎每一个主题,都做到本博客万千读者或七月学员所说的:“还是看校长的文章好理解”

如有任何问题或任何不懂的地方,可以随时留言/评论,我会找时间尽快回复

第一部分

// 待更

第二部分 DeepSeek-V2之MHA

2.1 DeepSeek-V2提出MHA的背景与作用

DeepSeek-V2由国内量化公司深度求索发布,参数规模为236B,其中每个token激活21B的参数,且支持128K的上下文,为提高其性能

  • 他们构建了一个高质量的多源预训练语料库,包括8.1T的token,与DeepSeek 67B使用的语料库相比,该语料库的数据量有所增加,特别是中文数据,并且数据质量更高
  • 他们首先在完整的预训练语料库上预训练DeepSeek-V2
    然后,收集了150万个对话会话,涵盖了数学、代码、写作、推理、安全等各个领域,以对DeepSeek-V2 Chat(SFT)进行监督微调(SFT)
    最后,我们遵循DeepSeekMath的方法,采用组相对策略优化(GRPO)进一步使模型与人类偏好对齐,并生成DeepSeek-V2 Chat(RL)

DeepSeek-V2主要有两大创新点,其在transformer架构的基础上

  1. 通过创造性的提出Multi-head Latent Attent——简称MLA,改进了传统多头注意力(Multi Head Attention),其本质意义便是降低了KV Cache的开销
  2. 且把FFN的结构改成DeepseekMoE——是对传统MoE结构的改进

2.1.1 KV Cache所导致的显存消耗大,需要尽可能降低

众所周知,KV Cache是大模型标配的推理加速功能——也是推理过程中,显存资源巨大开销的元凶之一。如下图所示,在模型推理时,KV Cache在显存占用量可达30%以上

目前大部分针对KV Cache的优化工作

  • 比如著名的vLLM(这是其介绍页面、这是其对应的GitHub、其论文则为:Efficient Memory Management for Large Language Model Serving with PagedAttention,当然了,我后面会专门写一篇解读vLLM的博客),其基于paged Attention,最大限度地利用碎片化显存空间,从而提升了空间利用率
  • 再比如GQA、MQA
    GQA是query数不变,但多个query组成一个group以共享一个key value
    MQA则query也不变,但所有query共享一个key、一个value

    至于更多,详见此文:一文通透各种注意力:从多头注意力MHA到分组查询注意力GQA、多查询注意力MQA

这类方案的问题是什么呢?在于比如前者并没有从根本上改变KV Cache占用空间巨大的问题,后者中的MQA虽然较大降低了KV cache计算量,但性能相比MHA下降太多了

那KV Cache到底是什么样的一个基本原理呢

  1. 对此,我们先来回顾下transformer当中的注意力计算公式(如对transformer还不够熟练,请看此文:Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT)\operatorname{Attention}(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V\operatorname{Attention}(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V
  2. GPT预测下一个token时,其只能看到待预测token之前的所有token,故在最终生成Q_1,Q_2,Q_3,Q_4整个序列的过程中,会涉及到如下计算过程\operatorname{softmax}\left(\begin{array}{cccc} Q_{1} K_{1}^{T} & -\infty & -\infty & -\infty \\ Q_{2} K_{1}^{T} & Q_{2} K_{2}^{T} & -\infty & -\infty \\ Q_{3} K_{1}^{T} & Q_{3} K_{2}^{T} & Q_{3} K_{3}^{T} & -\infty \\ Q_{4} K_{1}^{T} & Q_{4} K_{2}^{T} & Q_{4} K_{3}^{T} & Q_{4} K_{4}^{T} \end{array}\right)\left[\begin{array}{c} \overrightarrow{V_{1}} \\ \overrightarrow{V_{2}} \\ \overrightarrow{V_{3}} \\ \overrightarrow{V_{4}} \end{array}\right]
  3. 然后把上面的softmax结果和对应的V值一相乘,便可得到
    \begin{array}{l} \operatorname{Att}_{1}(Q, K, V)=\operatorname{softmaxed}\left(Q_{1} K_{1}^{T}\right) \overrightarrow{V_{1}} \\ \operatorname{Att}_{2}(Q, K, V)=\operatorname{softmaxed}\left(Q_{2} K_{1}^{T}\right) \overrightarrow{V_{1}}+\operatorname{softmaxed}\left(Q_{2} K_{2}^{T}\right) \overrightarrow{V_{2}} \\ \operatorname{Att}_{3}(Q, K, V)=\operatorname{softmaxed}\left(Q_{3} K_{1}^{T}\right) \overrightarrow{V_{1}}+\operatorname{softmaxed}\left(Q_{3} K_{2}^{T}\right) \overrightarrow{V_{2}} + \operatorname{softmaxed}\left(Q_{3} K_{3}^{T}\right) \overrightarrow{V_{3}} \\ \operatorname{Att}_{4}(Q, K, V)=\operatorname{softmaxed}\left(Q_{4} K_{1}^{T}\right) \overrightarrow{V_{1}}+\operatorname{softmaxed}\left(Q_{4} K_{2}^{T}\right) \overrightarrow{V_{2}} + \operatorname{softmaxed}\left(Q_{3} K_{3}^{T}\right) \overrightarrow{V_{3}} + \operatorname{softmaxed}\left(Q_{4} K_{4}^{T}\right) \overrightarrow{V_{4}} \end{array}
    可以很明显的看到,上述计算过程中,有不少的K V重复计算,比如K_{1}^{T}\overrightarrow{V_{1}}K_{2}^{T}\overrightarrow{V_{2}}K_{3}^{T}\overrightarrow{V_{3}}

如果序列长度越长,类似这样的K V重复计算会越多,从而势必将白白消耗那么大的显存,所以才说需要降低这种K V重复计算

2.1.2 Multi-head Latent Attent:致力于在推理中降低n_{h} d_{h}

MLA是对传统多头注意力做的改进,其目的有两个:首先是,降低推理过程中的KV Cache资源开销,其次,缓解MQA、MGA对性能的损耗

  • 如上文所说,KV Cache中,提到每一步都需要将K和V缓存下来。假设单个Attention Block块中的多头注意力,n个头(或n_h),每个kv的维度为d(或d_h),则每一步需要缓存的参数量为2 n_{h} d_{h} ll 表示为transformer的层数
  • 因此,MLA致力于在推理中降低n_{h} d_{h},具体而言,其不直接减少cache数量,而是类似Lora微调方法

    对Key和Value进行了一个低秩联合压缩(通过低秩转换为一个压缩的KV,使得存储的KV的维度显著减小),如下图所示

2.2 MLA的两个部分:一部分做压缩、一部分做RoPE编码

DeepSeek通过对Query和Key进行拆分为\left[q_{t}^{R}, q_{t}^{C}\right]\left[k_{t}^{R}, k_{t}^{C}\right],其中一部分做压缩\left(q_{t}^{C}, k_{t}^{C}\right)、一部分做RoPE编码\left(q_{t}^{R}, k_{t}^{R}\right)(R可以理解为RoPE的标识符)

2.2.1 MLA对query和key的压缩

对于上图右下角的\mathbf{c}_{t}^{K V}\mathbf{k}_{t}^{C}\mathbf{v}_{t}^{C}

可以看到先降维 再升维

\begin{array}{l} \mathbf{c}_{t}^{K V}=W^{D K V} \mathbf{h}_{t} \\ \mathbf{k}_{t}^{C}=W^{U K} \mathbf{c}_{t}^{K V} \\ \mathbf{v}_{t}^{C}=W^{U V} \mathbf{c}_{t}^{K V} \end{array}

  • 对于上述第一个公式,c_{t}^{K V} \in R^{d_{c}}是对key和value压缩后的隐向量,其通过一个降维映射矩阵W^{D K V} \in \mathbb{R}^{d_{c} \times d}和模型输入h_t其中,c_t的维度d_c远小于到头key和value的原始维度n_{h} d_{h},毕竟,别忘了上面说的,有n个头,每个kv的维度为d
  • 至于上述第二三公式,在于通过第一个公式得到c_t后,具体的key和value由两个对应的升维矩阵W^{U K}W^{U V}还原

    且在推理的过程中,只需要缓存每一步的\mathbf{c}_{t}^{K V},然后再计算还原回原始的K和V即可。由于c_t的维度远小于K、V。因此每一步token的推理产生的缓存由之前的2 n_{h} d_{h} l,变成d_c l

对于上图左下角的c_{t}^{Q}

之前提到KV Cache中,Q的作用只发生在当下(预测下一个token时,其只能看到待预测token之前的所有token),但是在模型训练的过程中,每个输入的token会通过多头注意力机制生成对应的query、key和value

这些中间数据的维度往往非常高,因此占用的内存量也相应很大

所以论文中也提到,为了降低训练过程中的激活内存activation memory,DeepSeek-V2还对queries进行低秩压缩,即便这并不能降低KV Cache,而其对Q的压缩方式和K、V一致,依然是先降维再升维

\begin{array}{l} c_{t}^{Q}=W^{D Q} h_{t} \\ q_{t}^{C}=W^{U Q} c_{t}^{Q} \end{array}

其中

  1. \mathbf{c}_{t}^{Q} \in \mathbb{R}^{d_{c}^{\prime}}是查询的压缩潜在向量(the compressed latent vector for queries)
  2. d_{c}^{\prime}\left(\ll d_{h} n_{h}\right)表示查询压缩后的维度,而W^{D Q} \in \mathbb{R}^{d_{c}^{\prime} \times d}W^{U Q} \in \mathbb{R}^{d_{h} n_{h} \times d_{c}^{\prime}}则分别表示查询的下投影和上投影矩阵(相当于先降维再升维)

2.2.2 MLA对query和key的RoPE编码

如下图红框所示,需要对\left(q_{t}^{R}, k_{t}^{R}\right)做RoPE编码,并对其中的Key位置编码的部分进行Cache,从而在推理时不需要对Key进行位置编码的计算,提高了推理效率

在RoPE的实现中,如果要让Q、K带上位置信息,会分别乘以相应的位置编码矩阵

\begin{array}{l} \tilde{Q}=R_{m} Q \\ \tilde{K}=R_{n} K \end{array}

如果计算QK时,自然就变成了

S=R_{m}^{T} Q^{T} R_{n} K

通过上一节可知,DeepSeek-V2对Q和K都进行了压缩:q_{t}^{C}=W^{U Q} c_{t}^{Q}\mathbf{k}_{t}^{C}=W^{U K} \mathbf{c}_{t}^{K V}

分别做了如下压缩

  • \begin{array}{l} \mathbf{c}_{t}^{K V}=W^{D K V} \mathbf{h}_{t} \\ \mathbf{k}_{t}^{C}=W^{U K} \mathbf{c}_{t}^{K V} \\ \mathbf{v}_{t}^{C}=W^{U V} \mathbf{c}_{t}^{K V} \end{array}
  • \begin{array}{l} c_{t}^{Q}=W^{D Q} h_{t} \\ q_{t}^{C}=W^{U Q} c_{t}^{Q} \end{array}

则整个过程变成

S=\left(W^{U Q}\right)^{T}\left(c_{t}^{Q}\right)^{T} R_{m}^{T} R_{n} c_{t}^{K V} W^{U K}

其中的W^{U Q}W^{U K}——如上节所述,分别是用于从低秩表示恢复到原始维度的解压缩矩阵(说白了,就是升维的)

  • 可有个问题,问题在于,由于低秩表示已经是压缩了的状态,故直接在c_{t}^{Q}c_{t}^{K V}上应用R_mR_n,不等价于在完整的Q和K上应用位置编码(因为压缩操作可能已经丢失了某些信息,使得位置编码不能直接和有效地反映原始Q和K的位置关系)
  • 为了解决这问题,Deepseek-V2设计了两个pe结尾的变量——\mathbf{q}_{t, i}^{R} \in \mathbb{R}^{d_{h}^{R}}\mathbf{k}_{t}^{R} \in \mathbb{R}^{d_{h}^{R}}(we propose the decoupled RoPE strategy that uses additional multi-head queries q𝑅𝑡,𝑖 ∈ R𝑑𝑅ℎ and a shared key k𝑅𝑡 ∈ R𝑑𝑅ℎ to carry RoPE, where d_{h}^{R} denotes the per-head dimension of the decoupled queries and key,即表示解耦查询和键的每头维度)

    用于储存旋转位置编码的信息,将信息存储和旋转编码解耦开
    \mathbf{q}_{t}^{R}=\operatorname{RoPE}\left(W^{Q R} \mathbf{c}_{t}^{Q}\right)
    \mathbf{k}_{t}^{R}=\operatorname{RoPE}\left(W^{K R} \mathbf{h}_{t}\right)
    其中,W^{Q R} \in \mathbb{R}^{d_{h}^{R} n_{h} \times d_{c}^{\prime}}W^{K R} \in \mathbb{R}^{d_{h}^{R} \times d}分别是用于生成解耦查询和键的矩阵

压缩完、且RoPE编码完之后,最后将这4个变量——q_{t}^{C}=W^{U Q} c_{t}^{Q}\mathbf{k}_{t}^{C}=W^{U K} \mathbf{c}_{t}^{K V}\mathbf{q}_{t}^{R}\mathbf{k}_{t}^{R},分别拼接起来,形成带信息压缩的Q、K,以及带位置信息的Q、K,进行最后的计算

为方便大家更好的理解,上述这些公式可以对比下 之前这个流程图

最终,单个Token产生的缓存包含了两个部分,即\left(d_{c}+d_{h}^{R}\right) l

其中,如上文说过的,n个头(或n_h),每个kv的维度为d(或d_h)l 表示为transformer的层数,n_g表示为GQA中的组数,d_{c}d_{h}^{R}分别表示MLA中解耦查询和键的KV压缩维度和没被压缩的每头维度

在DeepSeek-V2中,d_{c}被设置为4 d_{h}d_{h}^{R}被设置为\frac{d_{h}}{2},因此,它的KV缓存等于只有2.25组的GQA,但其性能强于MHA

// 待更

这篇关于一文通透DeepSeek-V2(改造Transformer的中文模型):从DeepSeek LLM到DeepSeek-V2的MLA与MoE的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1109982

相关文章

一文全面详解Python变量作用域

《一文全面详解Python变量作用域》变量作用域是Python中非常重要的概念,它决定了在哪里可以访问变量,下面我将用通俗易懂的方式,结合代码示例和图表,带你全面了解Python变量作用域,需要的朋友... 目录一、什么是变量作用域?二、python的四种作用域作用域查找顺序图示三、各作用域详解1. 局部作

RedisTemplate默认序列化方式显示中文乱码的解决

《RedisTemplate默认序列化方式显示中文乱码的解决》本文主要介绍了SpringDataRedis默认使用JdkSerializationRedisSerializer导致数据乱码,文中通过示... 目录1. 问题原因2. 解决方案3. 配置类示例4. 配置说明5. 使用示例6. 验证存储结果7.

一文彻底搞懂Java 中的 SPI 是什么

《一文彻底搞懂Java中的SPI是什么》:本文主要介绍Java中的SPI是什么,本篇文章将通过经典题目、实战解析和面试官视角,帮助你从容应对“SPI”相关问题,赢得技术面试的加分项,需要的朋... 目录一、面试主题概述二、高频面试题汇总三、重点题目详解✅ 面试题1:Java 的 SPI 是什么?如何实现一个

详解如何使用Python从零开始构建文本统计模型

《详解如何使用Python从零开始构建文本统计模型》在自然语言处理领域,词汇表构建是文本预处理的关键环节,本文通过Python代码实践,演示如何从原始文本中提取多尺度特征,并通过动态调整机制构建更精确... 目录一、项目背景与核心思想二、核心代码解析1. 数据加载与预处理2. 多尺度字符统计3. 统计结果可

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

一文详解PostgreSQL复制参数

《一文详解PostgreSQL复制参数》PostgreSQL作为一款功能强大的开源关系型数据库,其复制功能对于构建高可用性系统至关重要,本文给大家详细介绍了PostgreSQL的复制参数,需要的朋友可... 目录一、复制参数基础概念二、核心复制参数深度解析1. max_wal_seChina编程nders:WAL

一文详解如何查看本地MySQL的安装路径

《一文详解如何查看本地MySQL的安装路径》本地安装MySQL对于初学者或者开发人员来说是一项基础技能,但在安装过程中可能会遇到各种问题,:本文主要介绍如何查看本地MySQL安装路径的相关资料,需... 目录1. 如何查看本地mysql的安装路径1.1. 方法1:通过查询本地服务1.2. 方法2:通过MyS

使用Python自动化生成PPT并结合LLM生成内容的代码解析

《使用Python自动化生成PPT并结合LLM生成内容的代码解析》PowerPoint是常用的文档工具,但手动设计和排版耗时耗力,本文将展示如何通过Python自动化提取PPT样式并生成新PPT,同时... 目录核心代码解析1. 提取 PPT 样式到 jsON关键步骤:代码片段:2. 应用 JSON 样式到

python通过curl实现访问deepseek的API

《python通过curl实现访问deepseek的API》这篇文章主要为大家详细介绍了python如何通过curl实现访问deepseek的API,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编... API申请和充值下面是deepeek的API网站https://platform.deepsee

一文详解如何在Vue3中封装API请求

《一文详解如何在Vue3中封装API请求》在现代前端开发中,API请求是不可避免的一部分,尤其是与后端交互时,下面我们来看看如何在Vue3项目中封装API请求,让你在实现功能时更加高效吧... 目录为什么要封装API请求1. vue 3项目结构2. 安装axIOS3. 创建API封装模块4. 封装API请求