大模型基础面试——从序列进入transformer说起(一)

163 阅读4分钟

【本文正在参加金石计划附加挑战赛——第一期命题】

全网最通俗易懂的,最简洁的,从输入开始直到整个decoder-only架构结束全流程串讲

本篇阅读时间约2分钟

tips:可以多看下代码的注释

针对语言序列,图片那种batch norm的不涉及

尽可能简化表述,主要是为了准备面试突击,后续有时间再放图

序列输入模型(分词,embedding)

首先我们输入两句话:

  • “我爱你中华”
  • “你是我的眼。”

那么 它们经过tokenizer可能会变成

  • “我,爱你,中华”
  • “你,是,我的,眼,。”

假设会变成

  • (88,8,9)
  • (2,5,7,9,342)

那么我们都知道tokenizer其实是个词表映射,现在大模型很多词表都很大,比如128000以上的词表啦,65280的词表啦,llama2用的32000已经跟不上时代啦等等

那么上面的

  • (88,8,9)
  • (2,5,7,9,342)

数字其实代表索引。

embedding层的大小其实是(vocab_size, hidden_size)

我们理解说比如65280是词表大小,那么 hidden size就代表 embedding在使用hidden_size个维度特征在表示这个token。

我们随便找个大模型的config.json

{ |                                                             |
| - | ----------------------------------------------------------- |
|   | "architectures": [                                          |
|   | "XModelForCausalLM"                                         |
|   | ],                                                          |
|   | "auto_map": {                                               |
|   | "AutoConfig": "configuration_xmodel.XModelConfig",          |
    | "AutoModelForCausalLM": "modeling_xmodel.XModelForCausalLM" |
|   | },                                                          |
|   | "bos_token_id": 1,   # begin of sentence                                       |
|   | "eos_token_id": 2,   # end of sentece                                     |
|   | "hidden_act": "silu", #比较流行的swiglu这里都会写silu,我们看ffn源码就好                                      |
|   | "hidden_size": 2048,    
      # 这个就是d_model,传说中的模型维度,很多论文里说的模型维度就是指这个东西,
      # 它要和hidden_states相区别,hidden_states是计算出来的可变的隐藏状态,这东西和前向传播有关系,
      # d_model就是设置之后就不变啦,超级重要的                                    |
|   | "initializer_range": 0.02,                                  |
|   | "intermediate_size": 5632,  
    # 中间层,其实是ffn的隐藏维度,
    # 一般是d_model=hidden_size的45倍,用来升维,方便激活函数激活                     |
|   | "max_position_embeddings": 32768,    
        # 这个和rope有关系,后面结合代码新开一系列专门讲rope                       |
|   | "model_type": "xmodel",                                     |
|   | "num_attention_heads": 32,   # 多头,32个头                               |
|   | "num_hidden_layers": 24,                                    |
|   | "num_key_value_heads": 4,    
        # 这里kv只有4个头,分成了8组,其实个人感觉不是很好,压缩的过分了                               |
|   | "pad_token_id": 0,                                          |
|   | "pretraining_tp": 1,                                        |
|   | "rms_norm_eps": 1e-05,    # 这是rmsnorm里面防止分母为0的epsilon                                    |
|   | "tie_word_embeddings": false,                               |
|   | "torch_dtype": "float32",                                   |
|   | "transformers_version": "4.38.1",                           |
|   | "use_cache": true,     # 是否使用kv cache(一般用于gqa,mqa,mla等)                                      |
|   | "vocab_size": 32000    # 词表大小                                      |
|   | }

可以看到hidden_size是2048,词表vocab_size是32000

这样我们的第一个token88,其实在embedding输出就是找到第88行的2048维度的向量放到第一个位置, 依次类推,我们就得到了

这里要画个图

(2,5,2048)的输出

2 就是 batch size——因为我们有两句话嘛

5 就是这个batch里面的最大序列token长度 max_seq_len,那第一句话只有3的长度,空下来的就只能置 0 了,这就会造成很大的显存浪费,显卡怠惰怎么能行呢?(详情见kv cache系列)

2048 就是hidden_size,就是d_model,就是特征维度 (你是电你是光你是唯一的神话)

位置编码

如果不加位置信息,计算注意力分数其实是位置无关的,只是针对token之间计算而已,但是序列里位置信息很重要,在前面在后面表达的意思都可能截然不同,所以要引入位置编码。

绝对正弦编码sinPE会把单双数间隔地做cos和sin然后加到序列上面去

但是无法解决长程衰减问题

所以会有RoPE,但是这个东西它是在注意力qkv之后,计算注意力分数之前添加进去的,所以留着后面讲,我们先进入layer norm

层归一化

语言模型一般使用layer norm而且现在更多使用pre-LN

RMSNorm 就类似于 T5 的 layer norm

看现在我们的输入是(2,5,2048)

layer norm 就是针对(1,5,2048)做norm “我爱你中华”

而batch norm 是针对(2,1,2048)做norm ,这个就啼笑皆非了 它是对

  • “我爱你中华”
  • “你是我的眼。”

这两句话的 “我”和“你”做norm

都不是一个序列,怎么能做batch norm呢?你说对吧

具体的norm方法呢,rmsnorm有公式,大家可以自行检索 (这里也应该补个图,还有公式)

那么layer norm出来之后我们的特征维度的值域就会尽可能往[-1,1] 去靠了


下面就要进入大名鼎鼎的注意力机制啦

会在下一篇文章中叙述~

参考文献

找到的config.json