引子
在之前的学习过程中,总是听到有关KV Cache的话题,关于它有多么大,如何成为大模型推理的存储容量和带宽瓶颈,等等。 曾经的我对大模型推理[1]的原理和细节并无太大兴趣,也没有进行深入了解。 对KV Cache这个词,我望文生义的理解为一堆“Key-Value”的键值对,类似编程语言中“map”或是“dict”那样的数据结构。 这个暑假,由于后续的一些安排,我开始试图学习有关大模型推理原理的知识。我发现KV Cache的含义并不是我曾臆想的那样。因此,作为对我近期所学知识的梳理与总结,我打算在这篇文章中梳理一般的Transformer模型推理的计算过程,并简单说说KV Cache,以及其他的一些LLM领域的关键词,在这个计算过程中所处的位置。
不得不说,这个领域已经有无数篇文章了。我获取LLM相关知识的方式正是阅读这些文章以及观看一些科普视频。这其中当然还有LLM本身的协助。它们的寓教于乐能力显然强过我的笔力。因此,即便这篇文章是以“从头开始认识”的视角写成,我也很难建议任何人使用我这篇文章作为学习资料。写这篇文章的目的仅仅是方便我整理自己的粗浅思绪罢了。如果读者有任何想点评的,或是发现了什么疏漏和错误,非常欢迎你向我指出。
输出一段话
或许还是从输入输出开始说起吧。从日常使用中也能体验到,大语言模型的输出是逐字(准确的说是逐token)产生的。大语言模型的输出是一个迭代的过程。首先,它以用户提供的所有文本作为输入,经过一堆计算,输出它的第一个词(token)。随后,原始的输入,加上大语言模型刚刚输出的那第一个词,组合起来,再一次作为输入送入模型,经过计算,可以输出第二个词。如此往复,则可以输出完整的段落和文章。
1 | 第一次:[用户输入] -(模型计算)-> [第一个词] |
Q/A:
- 迭代输出,每次送入同一个模型?
是的。输出第二个词,所采用的模型结构、参数都和输出第一个词时完全一样,只是输入在原始基础上有追加。当然,这只是数学上的相同,并不是具体实现上的约束。
为了扫清理解的阻碍,有必要阐释token究竟是什么。Token是模型处理文本所采用的最小单位,在多数时候对应一个词、词的一部分、或者标点。 或许可以想象模型有一本token的“词典”。 GPT3模型有五万个[2]预设的token,也就是词典中有五万多个词。现在的先进模型,这个值大概在几万到几十万不等。模型只认识这些预设好的“词”,因此针对用户的文本输入,有必要进行“分词”(学名token化),将输入一个一个对应到已有的词典中。
Q/A:
哪些词会被视为token?
词典有多大,具体有哪些词,在模型训练之前就确定了。词典的大小是模型规模设计的一部分。而具体使用哪些词填充词典,则是设计者使用算法,根据大量文本的统计分布总结的。如果用户输入的词比较生僻,没有收录进模型的“词典”怎么办?
在常用词之外,还会有所有字节码对应的token作为备选。因此请放心,不会存在输入无法token化的情况。多国语言的相似词汇会如何处理?会合并为一个token吗?
只要字符不同(比如汉字和英文),不论含义是否相似,都将分配不同token[3]。多国语言抢占有限词典,这是词典制定时面临的现实。在统计语料的过程中,占比大的那种语言,更有可能分到更多的token。对于token少的那个语言,模型性能可能不佳,但就像第二问说的,总能有对应token。如果token词典出现问题,后续计算会不会受严重影响?
会的。一个典型例子是gpt4o时代的一个乌龙。4o采用的词表中“给主人留下些什么吧”9字对应一个token,导致模型根本没能理解这个token的含义,对这句话的翻译五花八门。
输出一个词
明确token的概念后,让我们将目光聚焦于输出大段文本这个多轮次过程的其中的一个轮次,即输出一个token的过程。
一个轮次的流程大概是这样:
- 首先,当然要对输入文本分词,按照词表拆分为一串token
- Embedding:将每个token变为一个高维度的向量表示。原输入的一堆token在这一步变为了一堆向量,它们可拼成一个矩阵,姑且记作
,其中的每一列[4]代表一个token向量; - Attention:是transformer最具代表性的步骤呢。具体细节先按下不表,其效果是更新了
的值; - 多层感知机(MLP) / 全连接层: 更像传统前馈神经网络。其效果是更新了
的值; - 重复:重复执行“Attention-MLP”的组合,
被逐渐修改。重复次数也即模型的“层数”,每层参数并不相同; - Unembedding:完成所有层计算后,理想情况下,此时
的最后一列[4],将包含模型对原始文本的下一个词的预测信息。取 的最后一列,将它翻译成最合适的token(依据token的概率分布选择),然后输出token对应的文本。
这只是流程的大致说明,接下来马上进行逐个的详细描述,因此这里没有Q/A哦。
Embedding & Unembedding
输入文本变为token序列后,第一步就是将每个token变为高维向量。在随后的计算中,这些高维向量的值会逐渐更新,直到完成所有层数的计算,取出更新后的,原文本末尾的token对应的向量,将它反向变回token,则完成了一个token的输出。这第一步就叫embedding,最后一步叫unembedding。
token变成的向量究竟有多少维? 这取决于模型的规模设计。以GPT3-175B为例,它的token向量维数是12288.
每个token会变成什么向量?
这是模型参数的一部分。这部分参数可以记作一个
这些参数有什么含义?在人工智能领域,询问参数的含义似乎永远无法得到明确的答复。这些参数是在训练过程中使用优化算法逐渐确定下来的,是计算的结果,并不具备什么人为赋予的含义。在推理时它们是固定已知量,不随输入文本改变。如果硬要深究它们的含义,只能说,这些高维向量的取值和方向,某种意义上代表了token的初步的语言含义。
值得补充的是,因为语言有严格的语序,单纯查表映射无法体现词的先后顺序。因此 Embedding 还有一个关键步骤叫位置编码(Positional Encoding),它会将 token 在文本中所处位置的信息“注入”到向量中。
经由embedding步骤,原始文本(已被转化为token的序列)变为了多个向量。这些向量按照文本顺序拼接起来,成为一个矩阵
描述完初始的Embedding步骤,现在描述末尾的Unembedding步骤。
经由多层Attention和前馈网络处理,
输出的
这样天然具备不错的效果。因为点乘可以提取两个向量的方向信息,如果
似乎Embedding和Unembedding两步能说的就这些。
也许读者还会有一个困惑:如果最终输出只在乎
一方面,从训练的角度,生成的
另一方面,从推理的角度,在模型推理过程中,
Attention
wip
前馈网络
wip- “推理”指的是模型参数已经固定,从用户输入经计算得到输出结果的过程,与之相对的概念是“训练”,即调整模型参数的过程。 ↩︎
- 50257 ↩︎
- 当然,不同语言中出现的重复部分,例如数字、符号、“iPhone”这样的专有词等,使用相同token。 ↩︎
- 也可以是每一行,仅仅是符号表示不同。我观看的3b1b视频采用了线性代数领域习惯的列向量表示,而人工智能领域似乎偏好行表示。后续如果切换,我会注明。 ↩︎
- W代表权重Weights,角标代表功能 ↩︎
- 在公式中采用了out。后续一些没那么值得命名的值我也会用out表示。 ↩︎
- 最终提取候选token的softmax函数具备一个“温度”量T,如果温度高,分布偏平缓,模型更可能选取次优备选词;如果温度低,分布偏尖锐,模型更优先选最优词。不同于固定的模型“参数”,这个量是调用模型时可调的。 ↩︎
- AI说的,有待查证 ↩︎