minimind/notebook/1_model.ipynb

729 lines
29 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"cells": [
{
"cell_type": "markdown",
"id": "f359c4f9-9082-4843-8874-4e7c6f3c7404",
"metadata": {},
"source": [
"# 1-Model"
]
},
{
"cell_type": "markdown",
"id": "2f5a93fb-2d09-4af4-9782-578357927842",
"metadata": {},
"source": [
"如今的主流大模型的架构大致分为稠密模型(Dense)和混合专家模型(MOE), 在这一板块,我们将围绕 MiniMind 系列模型的源代码展开学习。\n",
"\n",
"## MiniMind Dense Model\n",
"\n",
"MiniMind 系列模型在 Llama 3.1 的基础上设计,基于经典的 Transformer Deocder-Only 架构,其主要特点如下:\n",
"1. 采用 Pre-Norm 归一化方法,使用 RMSNorm 归一化函数。\n",
"2. 使用 SwiGLU 激活函数。\n",
"3. 旋转位置嵌入 (RoPE)\n",
"\n",
"作者提供了对其 MiniMind 模型结构的可视化:\n",
"\n",
"![](../images/LLM-structure.png)\n",
"\n",
"我们在上一节中完成了 Tokenizer 的学习,这一节我们关注 LLM 模型架构的具体实现。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "6cd2aa37-3460-4a64-8285-f888d62b99d5",
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torch.nn as nn\n",
"import torch.nn.functional as F"
]
},
{
"cell_type": "markdown",
"id": "acbb3ad8-5143-45ff-af16-8dcc0cc2cf53",
"metadata": {},
"source": [
"### Root Mean Square Layer Normalization (RMSNorm)\n",
"\n",
"RMSNorm 是对 LayerNorm 的一个改进, 没有做 re-center 操作(移除了均值项),可以看作 LayerNorm 在均值为零时的特例,使用平方根均值归一化降低噪声影响。\n",
"\n",
"- Layer Norm\n",
"\n",
"$$y = \\frac{x-E(x)}{\\sqrt{Var(x) + \\epsilon}} * \\gamma + \\beta$$\n",
"\n",
"假设输入张量形状为 (batch_size, sequence_length, embedding_dim),层归一化会对 embedding_dim 维度进行归一化操作,其中, $\\epsilon$ 是一个超参数,用于防止非法运算(分母为零), $\\gamma$, $\\beta$ 均为可学习参数。\n",
"\n",
"- RMS Norm\n",
"\n",
"$$a_i=\\frac{a_i}{RMS(a) g_i}, where RMS(a) = \\sqrt{\\frac{1}{n}\\sum^n_{i=1}a^2_i}.$$\n",
"\n",
"假设输入张量形状为 (batch_size, sequence_length, embedding_dim),RMS归一化同样对 embedding_dim 维度进行归一化,其中,$g_i$ 为可学习参数,用于对均方根归一化结果进行加权。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c52df1c5-1047-48d8-aa00-c0b576a87632",
"metadata": {},
"outputs": [],
"source": [
"class RMSNorm(torch.nn.Module):\n",
" def __init__(self, dim: int, eps: float):\n",
" super().__init__()\n",
" self.eps = eps\n",
" self.weight = nn.Parameter(torch.ones(dim))\n",
"\n",
" def forward(self, x):\n",
" return self.weight * (x.float() * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)).type_as(x)"
]
},
{
"cell_type": "markdown",
"id": "6207c186-9816-41fd-872d-d5116257b274",
"metadata": {},
"source": [
"### Rotary Position Embedding, RoPE\n",
"\n",
"旋转位置编码是一种能将相对位置信息集成到 self-attentio 中, 进而提升 transformer 架构性能的位置编码方式, 和绝对位置编码相比, RoPE 具有很好的外推性, 是目前的主流位置编码方式。\n",
"\n",
"外推性的解释, 通俗来说就是训练的时候限制了 512 的上下文长度,那么推理时如果面对超过该长度的文本,LLM 可能无法正确处理。\n",
"\n",
"- 绝对位置编码\n",
"\n",
"$$f_{t:t\\in\\{q,k,v\\}}(\\boldsymbol{x}_i,i):=\\boldsymbol{W}_{t:t\\in\\{q,k,v\\}}(\\boldsymbol{x}_i+\\boldsymbol{p}_i)$$\n",
"\n",
"其中编码向量 $p_i$ 的计算使用 Sinusoidal 函数:\n",
"\n",
"$$\\boldsymbol{p}_{i,2t}=\\sin\\left(k/1000^{2t/d}\\right), \\boldsymbol{p}_{i,2t+1}=\\cos\\left(k/1000^{2t/d}\\right)$$\n",
"\n",
"正如其名,绝对位置编码只考虑了输入序列中的绝对位置关系,对于 token 之间的相对信息则没有纳入考虑。\n",
"\n",
"- 旋转位置编码\n",
"\n",
"假定 query 和 key 的内积操作可以被函数 g 表示(有点像核函数),该函数 g 的输入是词嵌入向量 $x_m, x_n$ 和它们之间的相对位置 $m-n$:\n",
"\n",
"$$<f_q(x_m ,m), f_k(x_n, n)>=g(x_m, x_n, m, n)$$\n",
"\n",
"旋转位置编码就是找到一个使上式成立的位置编码方式。\n",
"\n",
"$$f_{\\{q,k\\}}\\left(\\boldsymbol{x}_m,m\\right)=\\boldsymbol{R}_{\\Theta,m}^d\\boldsymbol{W}_{\\{q,k\\}}\\boldsymbol{x}_m$$\n",
"\n",
"$$\\boldsymbol{R}_{\\Theta,m}^d=\\underbrace{\\begin{pmatrix}\\cos m\\theta_0&-\\sin m\\theta_0&0&0&\\cdots&0&0\\\\\\sin m\\theta_0&\\cos m\\theta_0&0&0&\\cdots&0&0\\\\0&0&\\cos m\\theta_1&-\\sin m\\theta_1&\\cdots&0&0\\\\0&0&\\sin m\\theta_1&\\cos m\\theta_1&\\cdots&0&0\\\\\\vdots&\\vdots&\\vdots&\\vdots&\\ddots&\\vdots&\\vdots\\\\0&0&0&0&\\cdots&\\cos m\\theta_{d/2-1}&-\\sin m\\theta_{d/2-1}&-\\sin m\\theta_{d/2-1}\\end{pmatrix}}_{\\boldsymbol{W}_m}(13)$$\n",
"\n",
"$$\\Theta=\\left\\{\\theta_i=10000^{-2(i-1)/d},i\\in[1,2,\\ldots,d/2]\\right\\}$$\n",
"\n",
"由于 $\\boldsymbol{R}_{\\Theta,m}^d$ 的稀疏性,直接使用矩阵乘法会浪费算力,因此采用下述方式实现:\n",
"\n",
"$$\\boldsymbol{R}_{\\Theta,m}^{d}\\boldsymbol{x}=\\begin{pmatrix}x_{0}\\\\x_{1}\\\\x_{2}\\\\x_{3}\\\\\\vdots\\\\x_{d-2}\\\\x_{d-1}\\end{pmatrix}\\otimes\\begin{pmatrix}\\cos m\\theta_{0}\\\\\\cos m\\theta_{0}\\\\\\cos m\\theta_{1}\\\\\\cos m\\theta_{1}\\\\\\vdots\\\\\\cos m\\theta_{d/2-1}\\\\\\cos m\\theta_{d/2-1}\\end{pmatrix}+\\begin{pmatrix}-x_{1}\\\\x_{0}\\\\-x_{3}\\\\x_{2}\\\\\\vdots\\\\-x_{d-1}\\\\x_{d-2}\\end{pmatrix}\\otimes\\begin{pmatrix}\\sin m\\theta_{0}\\\\\\sin m\\theta_{0}\\\\\\sin m\\theta_{1}\\\\\\sin m\\theta_{1}\\\\\\vdots\\\\\\sin m\\theta_{d/2-1}\\\\\\sin m\\theta_{d/2-1}\\end{pmatrix}\n",
"$$"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "5aae4194-3738-44a7-9c33-bbf964067f8a",
"metadata": {},
"outputs": [],
"source": [
"# compute pe in complex form\n",
"def precompute_pos_cis(dim: int, end: int=int(32 * 1024), theta: float=1e6):\n",
" \"\"\"\n",
" compute pe frequency in complex form.\n",
" \"\"\"\n",
" freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))\n",
" t = torch.arange(end, device=freqs.device)\n",
" freqs = torch.outer(t, freqs).float()\n",
" pos_cis = torch.polar(torch.ones_like(freqs), freqs)\n",
" return pos_cis\n",
"\n",
"def apply_rotary_emb(xq, xk, pos_cis):\n",
" def unite_shape(pos_cis, x):\n",
" ndim = x.ndim\n",
" assert 0 <= 1 < ndim\n",
" assert pos_cis.shape == (x.shape[1], x.shape[-1])\n",
" shape = [d if i == 1 or i == ndim - 1 else 1 for i, d in enumerate(x.shape)]\n",
" return pos_cis.view(*shape)\n",
"\n",
" xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))\n",
" print('original shape xq: {}'.format(xq.shape)) # 查看 xq 的 shape\n",
" print('ajusted shape xq: {}'.format(xq_.shape)) # 查看经过复数表示后 xq 的 shape\n",
" xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))\n",
" pos_cis = unite_shape(pos_cis, xq_)\n",
" print('ajusted shape pos_cis: {}'.format(pos_cis.shape)) # 查看位置编码旋转角的形状\n",
" xq_out = torch.view_as_real(xq_ * pos_cis).flatten(3)\n",
" xk_out = torch.view_as_real(xk_ * pos_cis).flatten(3)\n",
" return xq_out.type_as(xq), xk_out.type_as(xk)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "5f8376c4-5b00-4c72-9f23-912478db581c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(torch.Size([16, 32]), tensor(1.+0.j))"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"xq, xk = torch.randn((2, 16, 4, 64)), torch.randn((2, 16, 4, 64)) # (batch_size, sequence_length, num_heads, head_dim)\n",
"pos_cis = precompute_pos_cis(64, 16) # 计算旋转位置编码的旋转角(复数表示)\n",
"pos_cis.shape, pos_cis[0, 0]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "b40d0ec8-8752-4db9-a4fe-e746d39dc970",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original shape xq: torch.Size([2, 16, 4, 64])\n",
"ajusted shape xq: torch.Size([2, 16, 4, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 16, 1, 32])\n"
]
}
],
"source": [
"xq_rope, xk_rope = apply_rotary_emb(xq, xk, pos_cis)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "c03dd09c-9ccf-4716-bbd3-58a1314c05d5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(torch.Size([2, 16, 4, 64]), torch.Size([2, 16, 4, 64]))"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"xq_rope.shape, xk_rope.shape"
]
},
{
"cell_type": "markdown",
"id": "fa47eeb0-874e-4df3-bf91-5f79109af8ca",
"metadata": {},
"source": [
"### Attention\n",
"\n",
"Attention 是 Transformer 架构中的重要机制,它可以捕获长序列间的依赖关系,并依据注意力得分进行重要性建模。\n",
"\n",
"在 MiniMind 模型中,Attention Block 涉及几个重要机制。\n",
"\n",
"1. GQA\n",
"2. KV Cache\n",
"4. Pre-RMSNorm & RoPE\n",
"5. SwiGLU\n",
"6. FFN\n",
"\n",
"- GQA\n",
"\n",
"Group Querey Attention (GQA) 是对多头自注意力机制的扩展,通过提供计算效率和模型表达能力的灵活权衡,实现了查询头的分组。\n",
"\n",
"具体来说,GQA 将查询头分为 G 个组,每个组共享一个公共的键和值。\n",
"\n",
"![img](./demo/gqa.png)\n",
"\n",
"- KV Cache\n",
"\n",
"KV Cache 能够有效压缩大模型推理时的显存占用,在推理时,前面生成的字符不需要与后面的字符产生 attention,从而使得前面已经计算的 K 和 V 可以缓存起来。\n",
"\n",
"- SwiGLU\n",
"\n",
"$$\\text{SwiGLU}(x,W,V,b,c)=\\text{Swish}_1(xW+b)\\otimes(xV+c)$$"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e61394db-5afa-4421-ba77-30a9c50f0603",
"metadata": {},
"outputs": [],
"source": [
"from demo.LMConfig import LMConfig\n",
"from typing import Any, Optional, Tuple, List\n",
"import torch.nn as nn\n",
"import math\n",
"\n",
"def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:\n",
" bs, slen, n_kv_heads, head_dim = x.shape\n",
" if n_rep == 1:\n",
" return x\n",
" return (\n",
" x[:, :, :, None, :]\n",
" .expand(bs, slen, n_kv_heads, n_rep, head_dim)\n",
" .reshape(bs, slen, n_kv_heads * n_rep, head_dim)\n",
" )\n",
"\n",
"class Attention(nn.Module):\n",
" def __init__(self, args: LMConfig):\n",
" super().__init__()\n",
" self.n_kv_heads = args.n_heads if args.n_kv_heads is None else args.n_kv_heads\n",
" assert args.n_heads % self.n_kv_heads == 0\n",
" self.n_local_heads = args.n_heads\n",
" self.n_local_kv_heads = args.n_kv_heads\n",
" self.n_rep = self.n_local_heads // self.n_local_kv_heads\n",
" self.head_dim = args.dim // args.n_heads\n",
" self.wq = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)\n",
" self.wk = nn.Linear(args.dim, args.n_kv_heads * self.head_dim, bias=False)\n",
" self.wv = nn.Linear(args.dim, args.n_kv_heads * self.head_dim, bias=False)\n",
" self.wo = nn.Linear(args.n_heads * self.head_dim, args.dim, bias=False)\n",
" self.attn_dropout = nn.Dropout(args.dropout)\n",
" self.resid_dropout = nn.Dropout(args.dropout)\n",
" self.dropout = args.dropout\n",
" self.flash = hasattr(torch.nn.functional, 'scaled_dot_product_attention') and args.flash_attn\n",
" mask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float(\"-inf\"))\n",
" mask = torch.triu(mask, diagonal=1)\n",
" self.register_buffer(\"mask\", mask, persistent=False)\n",
"\n",
" def forward(self,\n",
" x: torch.Tensor,\n",
" pos_cis: torch.Tensor,\n",
" past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,\n",
" use_cache=False):\n",
" bsz, seq_len, _ = x.shape\n",
" ############## Forward QKV & RoPE ##############\n",
" xq, xk, xv = self.wq(x), self.wk(x), self.wv(x)\n",
" xq = xq.view(bsz, seq_len, self.n_local_heads, self.head_dim)\n",
" xk = xk.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim)\n",
" xv = xv.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim)\n",
" xq, xv = apply_rotary_emb(xq, xk, pos_cis)\n",
" ################### KV Cache ###################\n",
" if past_key_value is not None:\n",
" xk = torch.cat([past_key_value[0], xk], dim=1)\n",
" xv = torch.cat([past_key_value[1], xv], dim=1)\n",
" past_kv = (xk, xv) if use_cache else None\n",
" xq, xk, xv = (\n",
" xq.transpose(1, 2),\n",
" repeat_kv(xk, self.n_rep).transpose(1, 2),\n",
" repeat_kv(xv, self.n_rep).transpose(1, 2)\n",
" )\n",
" ############ Scaled Dot Production #############\n",
" if self.flash and seq_len != 1:\n",
" dropout_p = self.dropout if self.training else 0.0\n",
" output = F.scaled_dot_product_attention(\n",
" xq, xk, xv,\n",
" attn_mask=None,\n",
" dropout_p=dropout_p,\n",
" is_causal=True\n",
" )\n",
" else:\n",
" scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)\n",
" scores += self.mask[:, :, :seq_len, :seq_len]\n",
" scores = F.softmax(scores.float(), dim=-1).type_as(xq)\n",
" scores = self.attn_dropout(scores)\n",
" output = scores @ xv\n",
" ################################################\n",
" output = output.transpose(1, 2).reshape(bsz, seq_len, -1)\n",
" output = self.resid_dropout(self.wo(output))\n",
" return output, past_kv"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ca9f6675-7d70-49b4-ba02-f91109869fd8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"torch.Size([4, 16, 512])\n",
"torch.Size([16, 32])\n",
"original shape xq: torch.Size([4, 16, 8, 64])\n",
"ajusted shape xq: torch.Size([4, 16, 8, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 16, 1, 32])\n",
"torch.Size([4, 16, 512]) torch.Size([4, 16, 2, 64]) torch.Size([4, 16, 2, 64])\n"
]
}
],
"source": [
"attn = Attention(LMConfig())\n",
"x = torch.randn((4, 16, 512))\n",
"print(x.shape)\n",
"pos_cis = precompute_pos_cis(64, 16)\n",
"print(pos_cis.shape)\n",
"output, past_kv = attn(x, pos_cis=pos_cis, use_cache=True)\n",
"print(output.shape, past_kv[0].shape, past_kv[1].shape)"
]
},
{
"cell_type": "markdown",
"id": "495f1e81-36de-4e94-8ae4-69490f62d15f",
"metadata": {},
"source": [
"### FeedForward Network\n",
"\n",
"前向传播神经网络接收来自注意力层的输出,并对其做进一步的线性变换,以捕获更复杂的特征和表示。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "a16f3ece-3b4e-42aa-9b88-1a7193dd2bf0",
"metadata": {},
"outputs": [],
"source": [
"class FeedForward(nn.Module):\n",
" def __init__(self, config: LMConfig):\n",
" super().__init__()\n",
" if config.hidden_dim is None:\n",
" hidden_dim = 4 * config.dim\n",
" hidden_dim = int(2 * hidden_dim / 3)\n",
" config.hidden_dim = config.multiple_of * ((hidden_dim + config.multiple_of - 1) // config.multiple_of)\n",
" self.w1 = nn.Linear(config.dim, config.hidden_dim, bias=False)\n",
" self.w2 = nn.Linear(config.hidden_dim, config.dim, bias=False)\n",
" self.w3 = nn.Linear(config.dim, config.hidden_dim, bias=False)\n",
" self.dropout = nn.Dropout(config.dropout)\n",
"\n",
" def forward(self, x):\n",
" return self.dropout(self.w2(F.silu(self.w1(x) * self.w3(x))))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4fe2abd9-0c40-44be-b64d-8b0caa28604b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"torch.Size([4, 16, 512])"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ffn = FeedForward(LMConfig())\n",
"x = torch.randn((4, 16, 512))\n",
"output = ffn(x)\n",
"output.shape"
]
},
{
"cell_type": "markdown",
"id": "2317829c-39b8-44d5-beb2-1b9727bc35c8",
"metadata": {},
"source": [
"### MiniMind Block\n",
"\n",
"到目前为止,,已经完成了 Attention Layer 和 FeedForward Layer 的构建,所有必须的组件都已经具备,我们着手构建一个 MiniMind Block"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "d6c224a4-7ba5-4cf8-9297-311c660f5764",
"metadata": {},
"outputs": [],
"source": [
"class MiniMindBlock(nn.Module):\n",
" def __init__(self, layer_id: int, config: LMConfig):\n",
" super().__init__()\n",
" self.n_heads = config.n_heads\n",
" self.dim = config.dim\n",
" self.head_dim = config.dim // config.n_heads # number of GQA heads\n",
" self.attention = Attention(config)\n",
"\n",
" self.layer_id = layer_id\n",
" self.attention_norm = RMSNorm(config.dim, eps=config.norm_eps)\n",
" self.ffn_norm = RMSNorm(config.dim, eps=config.norm_eps)\n",
" self.feed_forward = FeedForward(config)\n",
"\n",
" def forward(self, x, pos_cis, past_key_value=None, use_cache=False):\n",
" h_attn, past_kv = self.attention(\n",
" self.attention_norm(x), # pre-normed x\n",
" pos_cis,\n",
" past_key_value=past_key_value,\n",
" use_cache=use_cache\n",
" )\n",
" h = x + h_attn # residual connection\n",
" out = h + self.feed_forward(self.ffn_norm(h)) # feed forward + residual connection\n",
" return out, past_kv"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "d23b4461-534c-4f6d-a00b-cdcfbd4d7bec",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original shape xq: torch.Size([4, 16, 8, 64])\n",
"ajusted shape xq: torch.Size([4, 16, 8, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 16, 1, 32])\n"
]
},
{
"data": {
"text/plain": [
"(torch.Size([4, 16, 512]),\n",
" torch.Size([4, 16, 2, 64]),\n",
" torch.Size([4, 16, 2, 64]))"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"miniblock = MiniMindBlock(1, LMConfig())\n",
"x = torch.randn((4, 16, 512))\n",
"pos_cis = precompute_pos_cis(64, 16)\n",
"out, past_kv = miniblock(x, pos_cis, use_cache=True)\n",
"out.shape, past_kv[0].shape, past_kv[1].shape"
]
},
{
"cell_type": "markdown",
"id": "f9bb3b2e-4d1d-4721-9908-546f3c49dde3",
"metadata": {},
"source": [
"### MiniMindLM\n",
"\n",
"以 MiniMind Block 为基本组件,我们可以对该 LLM 进行最后组装!"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "7267245b-f13a-4bcf-9358-5fdd89106153",
"metadata": {},
"outputs": [],
"source": [
"from transformers import PreTrainedModel\n",
"from transformers.modeling_outputs import CausalLMOutputWithPast\n",
"\n",
"class MOEFeedForward():\n",
" \"\"\"Place holder\"\"\"\n",
" pass\n",
"\n",
"class MiniMindLM(PreTrainedModel):\n",
" \"\"\"Notice: To simplify notebook run, set n_layers to 2\"\"\"\n",
" config_class = LMConfig\n",
"\n",
" def __init__(self, params: LMConfig = None):\n",
" self.params = params or LMConfig()\n",
" super().__init__(self.params)\n",
" self.vocab_size, self.n_layers = params.vocab_size, params.n_layers\n",
" # 映射:词表维度 -> 嵌入维度\n",
" self.tok_embeddings = nn.Embedding(params.vocab_size, params.dim)\n",
" self.dropout = nn.Dropout(params.dropout)\n",
" self.layers = nn.ModuleList([MiniMindBlock(1, params) for l in range(self.n_layers)])\n",
" self.norm = RMSNorm(params.dim, eps = params.norm_eps)\n",
" self.output = nn.Linear(params.dim, params.vocab_size, bias=False)\n",
" self.tok_embeddings.weight = self.output.weight\n",
" self.register_buffer(\n",
" \"pos_cis\",\n",
" precompute_pos_cis(dim=params.dim // params.n_heads, theta=params.rope_theta),\n",
" persistent=False\n",
" )\n",
" self.OUT = CausalLMOutputWithPast()\n",
"\n",
" def forward(self,\n",
" input_ids: Optional[torch.Tensor] = None,\n",
" past_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = None,\n",
" use_cache: bool = False,\n",
" **args):\n",
" past_key_values = past_key_values or [None] * len(self.layers) # save history KV\n",
" start_pos = args.get('start_pos', 0)\n",
" h = self.dropout(self.tok_embeddings(input_ids))\n",
" pos_cis = self.pos_cis[start_pos: start_pos + input_ids.size(1)]\n",
" past_kvs = []\n",
" for l, layer in enumerate(self.layers):\n",
" h, past_kv = layer(\n",
" h, pos_cis,\n",
" past_key_value=past_key_values[l],\n",
" use_cache=use_cache\n",
" )\n",
" past_kvs.append(past_kv)\n",
" logits = self.output(self.norm(h))\n",
" aux_loss = sum(l.feed_forward.aux_loss for l in self.layers if isinstance(l.feed_forward, MOEFeedForward)) # MOE 辅助损失\n",
" self.OUT.__setitem__('logits', logits)\n",
" self.OUT.__setitem__('aux_loss', aux_loss)\n",
" self.OUT.__setitem__('past_key_value', past_kvs)\n",
" return self.OUT\n",
"\n",
" @torch.inference_mode()\n",
" def generate(self, input_ids, eos_token_id=2, max_new_tokens=512, temperature=0.75, top_p=0.90,\n",
" stream=False, rp=1, use_cache=True, pad_token_id=0, **args):\n",
" # 流式生成 (返回生成器,自由控制输出)\n",
" if stream:\n",
" return self._stream(input_ids, eos_token_id, max_new_tokens, temperature, top_p, rp, use_cache, **args)\n",
" # 直接生成 (一步到位)\n",
" generated = []\n",
" for i in range(input_ids.size(0)):\n",
" non_pad = input_ids[i][input_ids[i] != pad_token_id].unsqueeze(0)\n",
" out = self._stream(non_pad, eos_token_id, max_new_tokens, temperature, top_p, rp, use_cache, **args)\n",
" tokens_list = [tokens[:, -1:] for tokens in out]\n",
" gen = torch.cat(tokens_list, dim=-1) if tokens_list else non_pad\n",
" full_sequence = torch.cat([non_pad, gen], dim=-1)\n",
" generated.append(full_sequence)\n",
" max_length = max(seq.size(1) for seq in generated)\n",
" generated = [\n",
" torch.cat([seq, torch.full((1, max_length - seq.size(1)), pad_token_id, dtype=seq.dtype, device=seq.device)],dim=-1) \n",
" for seq in generated\n",
" ]\n",
" return torch.cat(generated, dim=0)\n",
"\n",
" def _stream(self, input_ids, eos_token_id, max_new_tokens, temperature, top_p, rp, use_cache, **args):\n",
" start, first_seq, past_kvs = input_ids.shape[1], True, None\n",
" while input_ids.shape[1] < max_new_tokens - 1:\n",
" if first_seq or not use_cache: # 若第一次生成序列 or 无 KV Cache, 每次生成传入整个 token id 序列\n",
" out, first_seq = self(input_ids, past_key_values=past_kvs, use_cache=use_cache, **args), False\n",
" else: # 若非第一次生成 and 有 KV Cache,每次传入最后一个 token id 与 KV Cache 进行推理加速\n",
" out = self(input_ids[:, -1:], past_key_values=past_kvs, use_cache=use_cache,\n",
" start_pos=input_ids.shape[1] - 1, **args)\n",
" logits, past_kvs = out.logits[:, -1, :], out.past_key_values # logits.shape: (batch_size, seq_len, embed_dim),获取最后一位 logits\n",
" logits[:, list(set(input_ids.tolist()[0]))] /= rp # 对生成 token 进行惩罚,降低后续重复生成几率\n",
" logits /= (temperature + 1e-9) # 调整温度,控制生成多样性\n",
" if top_p is not None and top_p < 1.0: # top-p 采样\n",
" sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1)\n",
" sorted_probs = F.softmax(sorted_logits, dim=-1)\n",
" cumulative_probs = torch.cumsum(sorted_probs, dim=-1)\n",
" sorted_indices_to_remove = cumulative_probs > top_p\n",
" sorted_indices_to_remove[:, 1:] = sorted_indices_to_remove[:, :-1].clone()\n",
" sorted_indices_to_remove[:, 0] = False\n",
" indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)\n",
" logits[indices_to_remove] = -float('Inf')\n",
" input_ids_next = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1) # 从保留的 token 中采样\n",
" input_ids = torch.cat((input_ids, input_ids_next), dim=1)\n",
" yield input_ids[:, start:]\n",
" if input_ids_next.item() == eos_token_id:\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "dc7b4d79-a3aa-4143-aa5c-2abce34bc96c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original shape xq: torch.Size([1, 4, 8, 64])\n",
"ajusted shape xq: torch.Size([1, 4, 8, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 4, 1, 32])\n"
]
},
{
"data": {
"text/plain": [
"(torch.Size([1, 4, 6400]), 0, 1)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"MiniMind = MiniMindLM(LMConfig())\n",
"input_ids = torch.Tensor([1, 3, 5, 7]).long().reshape(1, 4)\n",
"OUT = MiniMind(input_ids, use_cache=True)\n",
"OUT.logits.shape, OUT.aux_loss, len(OUT.past_key_value)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "3480a860-e934-43de-a621-8063f0bdea2f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original shape xq: torch.Size([1, 4, 8, 64])\n",
"ajusted shape xq: torch.Size([1, 4, 8, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 4, 1, 32])\n",
"original shape xq: torch.Size([1, 1, 8, 64])\n",
"ajusted shape xq: torch.Size([1, 1, 8, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 1, 1, 32])\n",
"original shape xq: torch.Size([1, 1, 8, 64])\n",
"ajusted shape xq: torch.Size([1, 1, 8, 32])\n",
"ajusted shape pos_cis: torch.Size([1, 1, 1, 32])\n",
"torch.Size([1, 7]) tensor([[ 1, 3, 5, 7, 4867, 4289, 6268]])\n"
]
}
],
"source": [
"# 我们让 MiniMind 根据我们设计的 四个输入 token 生成输出\n",
"out = MiniMind.generate(input_ids, max_new_tokens=8, use_cache=True)\n",
"print(out.shape, out)"
]
},
{
"cell_type": "markdown",
"id": "b0e4ef5f-1358-4ffb-a150-61141ac3062f",
"metadata": {},
"source": [
"## Minimind MOE Model"
]
},
{
"cell_type": "markdown",
"id": "e54ced18-db3a-478b-9578-9825634f3d66",
"metadata": {},
"source": [
"@TODO"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.14"
}
},
"nbformat": 4,
"nbformat_minor": 5
}