通义千问Qwen1.5(Beta Version of Qwen2)代码理解

Qwen1.5项目地址:QwenLM/Qwen1.5: Qwen1.5 is the improved version of Qwen, the large language model series developed by Qwen team, Alibaba Cloud. (github.com)

从4.37.0版本开始,transformers集成了Qwen2的代码(官方称Qwen1.5是Qwen2的beta版本,在transformers的代码中也都是使用Qwen2这个名称),因此要使用Qwen2,需要transformers>=4.37.0,Qwen2的代码地址在transformers/models/qwen2目录下。

和Qwen一样,Qwen2仍然是一个decoder-only的transformer模型,使用SwiGLU激活函数、RoPE、多头注意力机制等。

位置编码

位置编码使用的是Rotary Position Embedding,它使用旋转矩阵对绝对位置进行编码,同时在自注意力公式中结合了明确的相对位置依赖,也就是说它将相对位置信息依赖集成到了self-attention中。因此该方法的位置编码是发生在注意力的计算过程中,并非之前的在输入tokens时将位置embedding和token embedding相加。

RoPE模块代码如下,主要是为了计算出RoPE方法中的cos值和sin值:

# Copied from transformers.models.mistral.modeling_mistral.MistralRotaryEmbedding with Mistral->Qwen2
class Qwen2RotaryEmbedding(nn.Module):
    def __init__(self, dim, max_position_embeddings=2048, base=10000, device=None):
        super().__init__()

        self.dim = dim
        self.max_position_embeddings = max_position_embeddings
        self.base = base
        inv_freq = 1.0 / (self.base ** (torch.arange(0, self.dim, 2, dtype=torch.int64).float().to(device) / self.dim))
        self.register_buffer("inv_freq", inv_freq, persistent=False)

        # Build here to make `torch.jit.trace` work.
        self._set_cos_sin_cache(
            seq_len=max_position_embeddings, device=self.inv_freq.device, dtype=torch.get_default_dtype()
        )

    def _set_cos_sin_cache(self, seq_len, device, dtype):
        self.max_seq_len_cached = seq_len
        t = torch.arange(self.max_seq_len_cached, device=device, dtype=torch.int64).type_as(self.inv_freq)

        freqs = torch.outer(t, self.inv_freq)
        # Different from paper, but it uses a different permutation in order to obtain the same calculation
        emb = torch.cat((freqs, freqs), dim=-1)
        self.register_buffer("cos_cached", emb.cos().to(dtype), persistent=False)
        self.register_buffer("sin_cached", emb.sin().to(dtype), persistent=False)

    def forward(self, x, seq_len=None):
        # x: [bs, num_attention_heads, seq_len, head_size]
        if seq_len > self.max_seq_len_cached:
            self._set_cos_sin_cache(seq_len=seq_len, device=x.device, dtype=x.dtype)

        return (
            self.cos_cached[:seq_len].to(dtype=x.dtype),
            self.sin_cached[:seq_len].to(dtype=x.dtype),
        )

得到cos值和sin值后,使用下面的函数进行绝对位置编码和集成相对位置信息依赖:

# Copied from transformers.models.llama.modeling_llama.rotate_half
def rotate_half(x):
    """Rotates half the hidden dims of the input."""
    x1 = x[..., : x.shape[-1] // 2]
    x2 = x[..., x.shape[-1] // 2 :]
    return torch.cat((-x2, x1), dim=-1)


# Copied from transformers.models.mistral.modeling_mistral.apply_rotary_pos_emb
def apply_rotary_pos_emb(q, k, cos, sin, position_ids, unsqueeze_dim=1):
    """Applies Rotary Position Embedding to the query and key tensors.

    Args:
        q (`torch.Tensor`): The query tensor.
        k (`torch.Tensor`): The key tensor.
        cos (`torch.Tensor`): The cosine part of the rotary embedding.
        sin (`torch.Tensor`): The sine part of the rotary embedding.
        position_ids (`torch.Tensor`):
            The position indices of the tokens corresponding to the query and key tensors. For example, this can be
            used to pass offsetted position ids when working with a KV-cache.
        unsqueeze_dim (`int`, *optional*, defaults to 1):
            The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
            sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
            that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
            k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
            cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
            the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
    Returns:
        `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
    """
    cos = cos[position_ids].unsqueeze(unsqueeze_dim)
    sin = sin[position_ids].unsqueeze(unsqueeze_dim)
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed

注意到,这里的实现跟论文中的实现有一点不一样:

  • 论文中是对输入向量 x m ∈ R d \bold{x}_m \in \R^d xmRd d d d个元素从左到右排列后每两个数组成一个二维向量,再对其应用相应的二维旋转矩阵,如下面的公式和示意图所示:

( x m ( 2 i − 1 ) ′ x m ( 2 i ) ′ ) = ( cos ⁡ m θ i − sin ⁡ m θ i sin ⁡ m θ i cos ⁡ m θ i ) ( x m ( 2 i − 1 ) x m ( 2 i ) ) , i ∈ [ 1 , … , d / 2 ] \left(\begin{array}{cc} {x_m^{(2i-1)}}^{\prime} \\ {x_m^{(2i)}}^{\prime} \end{array}\right) = \left(\begin{array}{cc}\cos m \theta_i & -\sin m \theta_i \\ \sin m \theta_i & \cos m \theta_i\end{array}\right) \left(\begin{array}{cc} x_m^{(2i-1)} \\ x_m^{(2i)} \end{array}\right), \quad i \in [1,\dots,d/2] (xm(2i1)xm(2i))=(cosmθisinmθisinmθicosmθi)(xm(2i1)xm(2i)),i[1,,d/2]

RoPE示意图

  • 这里的实现则是将 x m ∈ R d \bold{x}_m \in \R^d xmRd d d d个元素从左到右排列后分为前后相同长度的两部分,这两部分对应位置的元素组成一个二维向量,再对其应用相应的二维旋转矩阵,如下面的公式所示:

( x m ( i ) ′ x m ( i + d / 2 ) ′ ) = ( cos ⁡ m θ i − sin ⁡ m θ i sin ⁡ m θ i cos ⁡ m θ i ) ( x m ( i ) x m ( i + d / 2 ) ) , i ∈ [ 1 , … , d / 2 ] \left(\begin{array}{cc} {x_m^{(i)}}^{\prime} \\ {x_m^{(i+d/2)}}^{\prime} \end{array}\right) = \left(\begin{array}{cc}\cos m \theta_i & -\sin m \theta_i \\ \sin m \theta_i & \cos m \theta_i\end{array}\right) \left(\begin{array}{cc} x_m^{(i)} \\ x_m^{(i+d/2)} \end{array}\right), \quad i \in [1,\dots,d/2] (xm(i)xm(i+d/2))=(cosmθisinmθisinmθicosmθi)(xm(i)xm(i+d/2)),i[1,,d/2]

虽然这两种实现方式在对输入向量的元素的排列组合上有所差异(这里应该也是为了实现上的方便),但是原理是一样的,对结果也没影响,都能达到集成相对位置信息依赖的目的。

激活函数

激活函数只用在decoder模块的前馈网络中,用的SwiGLU(门控线性单元的一种变体),代码如下:

# Copied from transformers.models.mistral.modeling_mistral.MistralMLP with Mistral->Qwen2
class Qwen2MLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.hidden_size = config.hidden_size
        self.intermediate_size = config.intermediate_size
        self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
        self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
        self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
        self.act_fn = ACT2FN[config.hidden_act]

    def forward(self, x):
        return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))

这里面的config.hidden_act根据配置文件中的设置,其值为"silu",也就是SiLU(Sigmoid Linear Unit)函数:

SiLU ( x ) = x ∗ σ ( x ) , 其中  σ ( x )  为 Sigmoid 函数 \text{SiLU}(x) = x \ast \sigma(x), \quad \text{其中 } \sigma(x) \text{ 为 Sigmoid 函数} SiLU(x)=xσ(x),其中 σ(x)  Sigmoid 函数

SiLU函数也称为Swish函数,用Swish函数替换原始GLU中的Sigmoid,就得到了SwiGLU:

SwiGLU ( x , W , V ) = Swish ( x W ) ⊗ ( x V ) , ⊗ 表示元素乘积 \text{SwiGLU}(x, W, V) = \text{Swish}(xW) \otimes (xV),\otimes\text{表示元素乘积} SwiGLU(x,W,V)=Swish(xW)(xV)表示元素乘积

self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))这行代码就实现了使用SwiGLU激活函数的FFN的前向计算:

FFN SwiGLU ( x , W , V , W 2 ) = ( Swish ( x W ) ⊗ x V ) W 2 \text{FFN}_{\text{SwiGLU}}(x, W, V, W2) = (\text{Swish}(xW) \otimes xV)W2 FFNSwiGLU(x,W,V,W2)=(Swish(xW)xV)W2

注意力层:

  • 注意力机制有三种实现方式——手动实现、基于FlashAttention2(支持滑动窗口注意力)、基于SDPA(torch.nn.functional.scaled_dot_product_attention)
  • 实现了三种类型的注意力机制:MHA、MQA、GQA

decoder层:输入先使用RMSNorm,对注意力层进行残差连接后的输出使用RMSNorm,然后再输入给全连接层

掩码:causal mask结合输入的attention mask(区分实际输入和padding输入)

CausalLM:在最后的decoder层后加上全连接层

32B-Chat占用显存:大概63GB

待完善。。。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Elwin Wong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值