大模型知识点
一、大模型的基础知识
- 最初的输入(token 序列)是
input_ids
,形状通常为 [batch_size, seq_len],经过Embedding层后,通常变为 [batch_size, seq_len, hidden_dim]- 每个元素是一个词或子词的 ID(介于
[0, vocab_size)
)
- 每个元素是一个词或子词的 ID(介于
Q
的形状为 [batch_size, seq_len, hidden_dim]- 大词表模型最后有个线性层(
W^out
大小[hidden_dim, vocab_size]
),把每个时间步的 hidden state 投影到词表维度,最后flatten成[batch_size∗seqlen,vocab_size]。分类网络最后一层一般会选用softmax 和 cross entropy(s-p) 来计算损失
1.1 add、norm以及dropout
Add操作:残差连接就是把网络的输入和输出相加,即网络的输出为F(x)+x,在网络结构比较深的时候,网络梯度反向传播更新参数时,容易造成梯度消失的问题,但是如果每层的输出都加上一个x的时候,就变成了F(x)+x,对x求导结果为1,有效解决了梯度消失问题。
Norm操作:输入的词向量的形状是(x,y,z),x对应batch,y对应句子长度,z对应 词向量维度,Norm分有BN,LN,IN,具体可以看上方博客
Dropout:- Dropout在前向传播时,Dropout 会以某个概率 p 对输入随机置零,减少过拟合
- 缩放(Scaling)将未被归零的部分除以 (1−p)
- 在测试/推理阶段,神经网络通常关掉 Dropout(或改用推断模式的缩放),以保证所有神经元全部激活。
1.2 梯度导致的数据维度变化
$y=x^TA$,求导为$A^T$
正向传播和反向传播数据依赖:因为需要存储正向的所有中间结果
1.3 回归和分类的区别
回归是一个单连续值的输出,和真实值的区别作为loss
分类是多个输出,代表预测为第i类的置信度
下面几个内容为了学了deepseek的思想
1.4 RoPE
位置编码和相对位置有关,绝对位置不是那么重要 。RoPE位置编码的要点:
- 先做多Head的投影,再加上旋转位置编码
- 用的仍然是正弦和余弦函数操作,当两个向量计算内积时,直接转换成了包含相对位置信息。
- 只对Q,K做旋转位置编码,V不变
- d为embedding的维度,m为绝对位置
1.5 MLA(Multi-head Latent Attention)
[1.1有MHA的计算形式],对于每个token,KVCache的缓存占用为2$n_h * d_h * l(n_h为头的数量,d_h为每个头的维度),l为layer的数量$
实际上不存在两个上采样矩阵,可以直接从$c_t^{KV}$开始计算,WUK可以融入WQ,WUV可以融入WO,例如在算attention的时候,$q^Tk=h_q(W_1W_2)h_k$,训练的时候会直接得到括号里的内容
这种计算方式,对RoPE旋转位置编码是有影响的。也就是不能直接在压缩后的向量上应用RoPE(只对QK进行变换),那么可不可以在解压后的向量上应用RoPE呢?可以,但是影响效率,因为前面已经说过不显示计算解压后的向量,而是直接应用压缩后的向量。如何解决呢?再造一个向量,单独应用RoPE。
1.6 MoE
下面是计算路由的函数,sigmoid
route的行为是对输入进行一个linear,把linear的结果进行分组(取决于要激活的专家个数),在组内挑选得分最高的几个,求和代表为该组的得分,最后选出对应的组的index,最后再从组index中找出对应专家的index和专家权重
1.7 NSA(Native Sparse Attention)
推训一致,就是不需要和之前的所有词计算相关性,而只需要和其中某些最重要的词计算相关性。解决的是稀疏attention的问题
想象一下我们在阅读一个很长的文章的时候是如何做的?
- 先从头到尾大概浏览一下,大概知道哪些段落是我感兴趣的
- 逐字的精度我之前“标记”过的重点段落
- 对于我特别感兴趣的字句,再分别向前后扩展连续的语句,看看有没有更重要的内容。
通俗的解释,NSA就是利用了上面的三步的思想,解决了Sparse Attention的问题。
需要注意的是,压缩和sliding可以直接应用到flash attention中,但是中间的选择模块,每一个q会对应到不同的kv,需要用GQA,将选择到同一个KV的Q给group起来
1.7 Flash attentionsee_here
d为QKV_gen中的权重维度,Br和Bc的取值根据SRAM的大小,存储qkvo4份,计算如下
在GPU中,QKV存在HBM中,在计算softmax的时候,会分块加载到SRAM中。比如Q不断更新,K暂时不变,Q全部轮询完了之后,加载下一块K,继续更新Q直到S全部计算完毕
在这样的过程中,【 load:QKV,S ,P,V】 【store:S,P,O】 ,下面是没有经过flash attention优化的计算方式,需要三次读+写
但是flash attention可以让我们一次读+写,不需要再将PV存储到HBM,load QKV,在softmax阶段,存储一个全局的m和l,再修正S中的最大local值带来的误差,写回HBM的就是
外循环是KV层面,内循环是Q层面,循环完一个Q就刷完了O,循环KV的结果是不断刷新O(原基础上叠加每一行)
在SRAM上计算的变量如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
K, V: Bc*d
Q, O: Br*d ≤ d*d
S, P: Br*Bc ≤ d*Bc
l, m: Br ≤ d
注意,整个M的容量约是4*Bc*d
内循环是对O进行从上往下刷新,而外循环KV是重复刷新整个O,因此内循环load Q之后的计算,都会更新l,m,O
step1: 导入KV (+2*Bc*d)
step2: 导入Q, O, l, m (+2*d^2+2d)
step3: 计算S (+Bc*d) (共3*Bc*d+2*d(d+1))
step4: 释放QK (-2*Bc*d) (Bc*d+2*d(d+1))
step5: 用S计算m~ (+d),用S和m~计算P~ (+Bc*d)
step6: 释放S(-Bc*d)
step7: 利用P~计算l~ (+d)
step8: line11的m和l更新 (+2*d)
step9: 利用l^new, m^new, l, m, O, P~, V原地更新O并储存O (-d^2)
step10: 储存l_new, m^new (-2*d)
step11: 释放其他变量
1.8 ring attention
1.9 前向传播和反向传播
只有在分类任务里面,softmax和Loss是紧挨着的时候才会有这种巧妙的情况,像现在的transformer是用不上的,因为后面还有很多FFN
1.10 Boundry
compute time:受throughput限制,参数指标FLOPS = Floating point operations /s 每秒可以执行多少次浮点运算,例如4090可以达到82.58 TFLOPS
memory access latency:受bandwidth限制,但4090只能达到1.01TB/s ,加载数据的速度远远低于实际执行计算的速度
Computational Intensity: TFLOPS/B ,每次访问内存时要执行的浮点运算次数,描述了一个Roofline Model
1.11 分布式下的并行
- DP:数据拆分,模型复制,梯度聚合,权重更新后广播(单进程,多线程,只能利用一个CPU,单个进程读取mini-batch,切分n份给GPU)
- DDP:数据由不同的CPU以不同进程读取,分bucket实现梯度反向传播的边计算边传输,最后利用GPU之间的ring-allreduce实现同步(多线程读取多个mini-batch,在本地GPU进行计算)
- 模型太大,显存放不下–>DeepSpeed ZeRO-1(Zero Redundancy Optimizer):每个GPU存储全部的FP16的网络参数和梯度,但是优化器的所有参数(占比很大)按网络层数拆分到每个GPU。这样在边反向传播的时候可以边同步属于自己的梯度,更新各自的网络参数,最后广播给其他GPU实现全局的权重更新。 【DeepSpeed ZeRO-1将FP16梯度也拆分了,DeepSpeed ZeRO-3 进一步将FP16的梯度以及网络参数也拆分】—— DeepSpeed ZeRO-3由于缺少部分FP16模型参数,在前向和后向传播的时候,需要拥有该参数的GPU进行广播,可以overlap广播和计算的时间。
举个例子,存在GPU012的时候,前向传播的模型参数需要靠拥有该参数的GPU0广播给1,2。而反向传播时,GPU0,1计算完本地的FP16梯度,被GPU2 all-gather到本地,实现梯度聚合
ZeRO1和2的进程总传入/传出为2X(X为模型参数量),而ZeRO3为3X,但是降低了更多的显存占用
在分布式场景下,性能的考虑因素:计算b个样本梯度的时间$t_1$,传输m个参数或者梯度的传输时间$t_2$,每个batch的耗时为max(t1,t2),增大batchsize(通过增大b和GPU的数量n)会使得收敛变慢(可以调小学习率),但是还是要更多的epoch,导致训练时间增大(总结就是,我用了更多的计算单元,我训练单次时间减少了,但是为了收敛到更好,我需要更多的训练次数,我的训练时间还是没有降低)
张量并行(层内并行,包含所有层,但是每一层只含一部分的参数)
列并行,会切分权重但不切分输入,好处是可以让硬件并行。虽然计算attention不需要allreduce,但是后面的Dropout还是需要allreduce
行并行,输入和权重都会切分
需要两次通讯,第一次是broadcast输入X,第二次是allreduce的g,同步dropout之后的结果
流水线并行(层间并行,切割MLP的层数):只适合于ringGpipe 论文论文mini_batch把一个大的batch拆分成多个小的batch,只存储输入X,反向传播需要的中间激活值可以通过X重新进行计算(计算换空间),这样反向传播不需要储存每一层的中间激活值。
需要注意的是,一次额外的前向计算开销占整个系统计算开销的1/3
PipeDream使用非交错式1F1B,没有减少bubble但是节省了峰值显存。
二、Switch Transformers
“专家权重”或“可信度”通常由一个 路由器 (Router) 或 门控网络 (Gating Network) 来计算:
每个 token先经过前面的自注意力层与 Add+Norm 等操作后,得到一段向量h。路由器常常只是一个 单层线性变换(有时会加上激活函数),把 h 投影到“专家数”维度上。获得向量z,z 的每个分量对应一个专家(Expert)在“未归一化”下的得分,将 z 做 softmax 得到概率分布$p_i$,对应第i个专家的可信度。
在多核(或多GPU/TPU)环境中对大型模型进行并行化的split方案:
第一行:模型权重在各核心之间的切分方式、第二行:输入数据在各核心之间的切分方式。同一个颜色 / 同一个大矩形往往表示的是同一个权重矩阵的某一块;
- Data Parallelism:每个设备(核心)都持有完整的模型参数,但只处理一部分的数据(即 batch 切分)
- Model and Data Parallelism:不同的核心各自保存不同的一部分参数。大家合起来才构成完整模型。不同颜色一般是用来区分同一个大批次(batch)里被切分开的不同子块。所有颜色合起来才是同一次 forward/backward 的整个 batch ,换句话说,整张图中的所有颜色块加起来才构成一次完整的 batch;在实际训练中,我们往往会在每个训练 step 中取一个大的 batch,然后按照硬件并行策略把它拆分给各核心,各核心并行处理后再在梯度或激活等环节进行必要的通信与汇总。
- Expert and Data Parallelism:每个小颜色方块可以看作一个**专家(Expert)对应的参数 ** ,但“非专家”部分(如自注意力层、嵌入层等)通常会完整复制,
三、deepseek中的pytorch代码
基本的一些函数
1
2
3
4
5
6
7
8
9
10
11
12
#1. torch.topk 函数用于返回输入张量中指定维度上的前 k 个最大元素及其对应的索引
values, indices = torch.topk(x, k=2, dim=1)
#2. scatter_ :根据indices索引,讲第一个参数的值分散到目标张量的指定位置
torch.zeros([3,3]).scatter_(1, indices, True)
#3. unsqueeze操作,它的主要作用是在指定的维度上插入一个大小为1的新维度,从而改变张量的形状
y = torch.unsqueeze(x, dim=0)
#4. gather可以根据给定的索引从输入张量中收集元素,从而构建一个新的张量。(与scatter互为反操作)
output = torch.gather(input_tensor, dim=1, index=index_tensor)
#5. bincount用于统计非负整数张量中每个值出现的次数。
input_tensor = torch.tensor([1, 1, 2, 2, 10])
output = torch.bincount(input_tensor)
output: tensor([0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1]) #0-10
ner_datasets[“train”].features 可以查看数据集的特征