LLAMA

2025727

16:36

 

为什么需要norm?或者说神经网络为什么需要norm

神经网络中每一层的输出也是下一层的输入,反向传播更新参数后,由于w的更新,会使得每一层的输出分布发生变化 ,也就是下一层的输入分布发生了变化 ,导致下一层还要根据输入的变化再调整本层的参数w,这使得训练变得缓慢且网络预测时也会不稳定。norm可以使得每一层的输入分布变化不会过大,使得网络更加稳定。

神经网络内部节点(神经元)的分布变化 被称为Internal Covariate Shift,内部协变量漂移

 

RMSNorm

 

RMSnorm认为layernorm的成功主要是由于re-scaling invariance,而不是re-centering invariance,所以去掉了去中心化这一步骤。RMSnorm的好处有:1)计算量少,不需要计算均值。2)在实际应用中效果好。

 

相对位置编码

相对位置编码告诉注意力机制两个token之间的距离。给定两个token,我们创建表示它们之间距离的向量,即下图中的

 

Rope

作者:追一科技的苏剑林

出发点:计算attention score时,使用的是qk的内积,Can we find a inner product over q and k that only depends on the two vectors and the relative distance of the two tokens they represent.

 

事实上,可以找到这样的函数:

 

GPU is too fast

运算速度的bottleneck不在于计算量,而在于数据传输量。这意味着,我们的目标不仅是优化算法执行的计算量,而是最小化memory access/transfer

 

SwiGLU

论文《Gaussian Error Linear Units(GELUs)》提出了GELU,这是ReLU的平滑版本。

论文《Swish: a Self-Gated Activation Function》提出了Swish,这也是对带有非零负值梯度的ReLU平滑版本。Swish同样是个处处可微的非线性函数,且有一个参数beta用于控制函数的形状.

GLU(Gated Linear Units)其实不算是一种激活函数,而是一种神经网络层。它是一个线性变换后面接门控机制的结构。其中门控机制是一个sigmoid函数用来控制信息能够通过多少。

 

 

GQA 源码

#核心就是这个repeat_kv函数,将kv重复n_rep次,从而和q的头数匹配。

def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:

    """torch.repeat_interleave(x, dim=2, repeats=n_rep)"""

    bs, slen, n_kv_heads, head_dim = x.shape

    if n_rep == 1:

        return x

    return (

        x[:, :, :, None, :]  #在倒数第二维中增加一维,相当于unsqueeze(-1)

        .expand(bs, slen, n_kv_heads, n_rep, head_dim)

        .reshape(bs, slen, n_kv_heads * n_rep, head_dim)

    )

 

class Attention(nn.Module):

    """Multi-head attention module."""

 

    def __init__(self, args: ModelArgs):

        """

        Initialize the Attention module.

 

        Args:

            args (ModelArgs): Model configuration parameters.

 

        Attributes:

            n_kv_heads (int): Number of key and value heads.

            n_local_heads (int): Number of local query heads.

            n_local_kv_heads (int): Number of local key and value heads.

            n_rep (int): Number of repetitions for local heads.

            head_dim (int): Dimension size of each attention head.

            wq (ColumnParallelLinear): Linear transformation for queries.

            wk (ColumnParallelLinear): Linear transformation for keys.

            wv (ColumnParallelLinear): Linear transformation for values.

            wo (RowParallelLinear): Linear transformation for output.

            cache_k (torch.Tensor): Cached keys for attention.

            cache_v (torch.Tensor): Cached values for attention.

 

        """

        # ColumnParallelLinear是一个在大规模并行训练中使用的术语,特别是在训练大型的深度学习模型,

        # 如Transformer模型时。在模型并行训练中,一个大型的矩阵(例如神经网络的权重矩阵)会被分割成不同的列,

        # 并分散到不同的计算设备(如GPU)上。

        #

        # 在ColumnParallelLinear的情况下,每个计算设备存储权重矩阵的一部分列,而不是整个矩阵。

        # 每个设备计算它自己的前向传播部分,并将结果发送给其他设备以进行进一步的处理或合并结果。

        # 对于反向传播和梯度计算,每个设备计算其自己列的梯度,并可能需要与其他设备交换信息以更新权重。

        #

        # 这种方式可以显著减少每个设备上的内存需求,并允许训练更大的模型,因为模型的不同部分可以分布在多个设备上。

        # ColumnParallelLinear和RowParallelLinear(另一种将权重矩阵按行划分的方法)是实现模型并行的两种常见策略。

    

        super().__init__()

        self.n_kv_heads = args.n_heads if args.n_kv_heads is None else args.n_kv_heads

        model_parallel_size = fs_init.get_model_parallel_world_size()

        self.n_local_heads = args.n_heads // model_parallel_size

        self.n_local_kv_heads = self.n_kv_heads // model_parallel_size

        self.n_rep = self.n_local_heads // self.n_local_kv_heads

        self.head_dim = args.dim // args.n_heads

 

        self.wq = ColumnParallelLinear(

            args.dim,

            args.n_heads * self.head_dim,

            bias=False,

            gather_output=False,

            init_method=lambda x: x,

        )

        self.wk = ColumnParallelLinear(

            args.dim,

            self.n_kv_heads * self.head_dim,

            bias=False,

            gather_output=False,

            init_method=lambda x: x,

        )

        self.wv = ColumnParallelLinear(

            args.dim,

            self.n_kv_heads * self.head_dim,

            bias=False,

            gather_output=False,

            init_method=lambda x: x,

        )

        self.wo = RowParallelLinear(

            args.n_heads * self.head_dim,

            args.dim,

            bias=False,

            input_is_parallel=True,

            init_method=lambda x: x,

        )

        # kv_cache是缓存键值对,在训练过程中,我们只保存最近n个键值对

        self.cache_k = torch.zeros(

            (

                args.max_batch_size,

                args.max_seq_len,

                self.n_local_kv_heads,

                self.head_dim,

            )

        ).cuda()

        self.cache_v = torch.zeros(

            (

                args.max_batch_size,

                args.max_seq_len,

                self.n_local_kv_heads,

                self.head_dim,

            )

        ).cuda()

 

    def forward(

            self,

            x: torch.Tensor,

            start_pos: int,

            freqs_cis: torch.Tensor,

            mask: Optional[torch.Tensor],

    ):

        """

        Forward pass of the attention module.

 

        Args:

            x (torch.Tensor): Input tensor.

            start_pos (int): Starting position for caching.

            freqs_cis (torch.Tensor): Precomputed frequency tensor.

            mask (torch.Tensor, optional): Attention mask tensor.

 

        Returns:

            torch.Tensor: Output tensor after attention.

 

        """

        # 假设当前x为(1, 1, dim),也就是上一个预测的token

        # self-attention的输入,标准的(bs, seqlen, hidden_dim)

        bsz, seqlen, _ = x.shape

        # 计算当前token的qkv

        # q k v分别进行映射,注意这里key, value也需要先由输入进行映射再和kv_cache里面的key, value进行拼接

        xq, xk, xv = self.wq(x), self.wk(x), self.wv(x)

       

        xq = xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)

        xk = xk.view(bsz, seqlen, self.n_local_kv_heads, self.head_dim)

        xv = xv.view(bsz, seqlen, self.n_local_kv_heads, self.head_dim)

 

        # 对当前输入的query和key进行RoPE,注意kv_cache里面的key已经做过了RoPE

        xq, xk = apply_rotary_emb(xq, xk, freqs_cis=freqs_cis)

 

        # 缓存当前token的kv

        self.cache_k = self.cache_k.to(xq)

        self.cache_v = self.cache_v.to(xq)

        self.cache_k[:bsz, start_pos: start_pos + seqlen] = xk  # start_pos是推理时需要计算的token的起始位置,seqlen1(如果 是一个一个token预测的话)

        self.cache_v[:bsz, start_pos: start_pos + seqlen] = xv

 

        # 取出前seqlen个token的kv缓存

        # 取出全部缓存的key和value(包括之前在cache里面的和本次输入的),作为最终的key和value

        keys = self.cache_k[:bsz, : start_pos + seqlen]

        values = self.cache_v[:bsz, : start_pos + seqlen]

 

        # 将kv重复填充,使kv和q的头数个数相同

        # repeat k/v heads if n_kv_heads < n_heads,对齐头的数量

        keys = repeat_kv(keys, self.n_rep)  # (bs, cache_len + seqlen, n_local_heads, head_dim)

        values = repeat_kv(values, self.n_rep)  # (bs, cache_len + seqlen, n_local_heads, head_dim)

       

        # 计算当前token的attention score,,注意mask需要加上,另外维度要对应上

        xq = xq.transpose(1, 2)  # (bs, n_local_heads, seqlen, head_dim)

        keys = keys.transpose(1, 2)  # (bs, n_local_heads, cache_len + seqlen, head_dim)

        values = values.transpose(1, 2)  # (bs, n_local_heads, cache_len + seqlen, head_dim)

        scores = torch.matmul(xq, keys.transpose(2, 3)) / math.sqrt(self.head_dim)

        if mask is not None:

            scores = scores + mask  # (bs, n_local_heads, seqlen, cache_len + seqlen)

        scores = F.softmax(scores.float(), dim=-1).type_as(xq)

        output = torch.matmul(scores, values)  # (bs, n_local_heads, seqlen, head_dim)

        output = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)

        return self.wo(output)

 

 

 

 

 

 

已使用 OneNote 创建。