Byte-Pair Encoding是什么BPE是现代大模型中实现Tokenization(分词)的一种主流方法。为什么需要它先从问题出发,LLM的目标是输入文本,输出文本,但是模型是通过数学运算运转的,因此需要先将文本转化为数字进行计算。有几种自然的想法:一个字分配一个id,缺点是文本序列会变得很长,尤其对于英文文本而言,“hello”会占用5个token。Transformer模型的注意力机制计算复杂度与序列长度的平方成正比(O(N²)),过长的序列会极大地拖慢训练和推理速度。一个词分配一个id,缺点是词表会变得非常庞大,更致命的是,模型只能理解训练时见过的词。一旦遇到新词、拼写错误、专业术语或网络俚语,模型就束手无策了。因此需要寻找一种方法,规避上述的缺点。具体是怎么做的unicode是一个涵盖各语言的庞大字典,定义了UTF-8、UTF-16、UTF-32等编码格式,其中UTF-8因其高效与兼容性强的特点成为互联网的主流。同样的,BPE通常也使用UTF-8进行编码。下面举个例子。比如要将【hello】进行编码:先将单词分解成字节流【b’h’, b’e’, b’l’, b’l’, b’o’】,对于中文或一些特殊的字符而言,一个字由多个字节编码,一个字符的字节也会被拆分成单独的。寻找相邻的字节【b’he’, b’el’, b’ll’, b’lo’】,并将其中频率最高的提取出来,如果遇到频率最大且相同的,一般会选择第一个遇到的,或者字典序更大的,这里就取b’he’。更新字节流【b’he’, b’l’, b’l’, b’o’】重复这样的步骤,维护一个merges表,记录每次合并的规则,比如【b’h’, b’e’】,维护一个vocab表,编号0255对应字节0255,留下基础字节,后续编号按照合并的字节扩充,比如【256: b’he’】,直到将vocab的数量扩充到目标大小。通过这样的方式,让序列长度和词表大小都更加可控,并且由于将所有词都先用UTF-8编码,即使遇到没有见过的词,也可以用0~255的基础字节表示。不过从语义上来说,单个token并不一定具有明确含义。实现细节在处理前要先对文本进行预分词,正确处理标点符号、空格、换行等字符,用一个字典统计{词: 出现次数}。假设目标vocab大小是5000,初始的vocab大小为256个基础字节+特殊token(比如[end_of_sequence]),这里先要统计每个pair的次数,遍历{词: 出现次数},用{pair: 出现次数}做统计,找出频率最高的pair。在merges表中加入频率最高的pair后,需要更新{pair: 出现次数},可以选择再遍历一次{词: 出现次数},并且还要按照merges规则处理词,更好的办法是增量更新,额外维护一个字典,记录{pair: 词},这样我就可以知道每次更新时影响了哪部分词。找到这次更新相关的{pair: 词},遍历受影响的词在{词: 出现次数}中的键值对,统计变化的pair和次数,更新在{pair: 出现次数}和{pair: 词}中,并剔除不再存在的pair,可以高效的完成更新。Tokenizer根据上面的BPE的细节,所谓Tokenizer的作用其实就是先将文本字节化,按照BPE阶段的merges表合并按照vocab表找到对应编号(token id),将文本数字化实现细节同样要先将文本预分词,输入string,输出list按照merges规则,将每个分词的字节合并,暴力的方法就是遍历merges表,然后遍历整个预分词的list,双重循环处理。更快的方法是维护一个{pair: list[index]}的字典,按照merges表获取预分词的索引,按照规则替换预分词列表中的内容,然后统计新的预分词中的pair,更新{pair: list[index]}字典,最后在字典中删除这一次merge的pair。面对大文本要使用流式处理,对于生成的大token序列数据,也要存成一个易于流式处理的格式,便于后续的训练。Transformer language modelpytorch向量与矩阵运算在线性代数的课本上,向量与矩阵运算的公式一般写为y = W x y = Wxy=Wx,其中x xx为列向量,但是在计算机中,x xx一般被创建为行向量。为了与数学上的定义一致,在pytorch中的向量与矩阵相乘被定义为y = x W T y = xW^Ty=xWT。以线性神经网络为例,矩阵实际被存储为out_dim行,in_dim列,只是在计算时会进行转置。Relative Positional Embeddings ( RoPE )RoPE的思路是对每个token在embedding维度上编码,每两个做一次旋转,旋转角度由sequence位置和d k d_kdk​位置决定。像时钟的指针一样去拨动不同位置的隐藏特征,一方面,这可以保证位置编码的不重复,另一方面,不管两个token的绝对位置如何变化,只要相对位置不变,二者相差的角度就不变,增强了稳定性与泛化特性。将绝对位置编码的可并行性、相对位置编码的长度外推能力和零额外参数的高效性结合在了一起。在实现上,旋转矩阵只与位置有关,因此可以在初始化时预先存好,后续直接索引使用。每对参数的运算公式为y o u t = [ x 1 ∗ c o s − x 2 ∗ s i n , x 1 ∗ s i n + x 2 ∗ c o s ] y_out = [x_1*cos - x_2*sin, x_1*sin + x_2*cos]yo​ut=[x1​∗cos−x2​∗sin,x1​∗sin+x