LLaMA 3 是继 Mistral 之后最有前途的开源模型之一,可以解决广泛的任务。我之前在 Medium 上写过一篇关于使用 LLaMA 架构从头开始创建具有超过 230 万个参数的 LLM 的博客。现在 LLaMA-3 已发布,我们将以更简单的方式重新创建它。
我们不会在本博客中使用 GPU,但您至少需要 17 GB RAM,因为我们将加载一些大小超过 15 GB 的文件。如果这对您来说是个问题,您可以使用 Kaggle 作为解决方案。由于我们不需要 GPU,Kaggle 提供 30 GB RAM,同时仅使用 CPU 内核作为加速器。
以下是博客链接,指导您如何从头开始创建 2.3+ 百万参数 LLM:2.3+ 百万参数 LLM From Scratch
好的部分是我们不会使用面向对象编程(OOP)编码,而只是简单的 Python 编程。但是,您应该对神经网络和 Transformer 架构有基本的了解。这是博客所需遵循的唯一两个先决条件。
话题 | 关联 |
---|---|
变压器理论 | 视频链接 |
神经网络理论 | 视频链接 |
Python 基础知识 | 视频链接 |
在研究技术细节之前,您首先必须了解的是,LLaMA 3 的整个架构与 LLaMA 2 相同。因此,如果您还没有了解 LLaMA 3 的技术细节,那么就不会了解 LLaMA 3 的技术细节。您关注此博客的问题。即使您不了解 LLaMA 2 架构,也不必担心,我们也会查看其技术细节的高级概述。无论哪种方式,这个博客都是为您设计的。
以下是关于 LLaMA 2 和 LLaMA 3 的一些要点。如果您已经熟悉它们的架构:
特征 | 骆驼3 | 骆驼2 |
---|---|---|
分词器 | Tiktoken(由 OpenAI 开发) | 句子片段 |
参数数量 | 8B、70B | 70B、13B、7B |
训练数据 | 15T代币 | 2.2T代币 |
上下文长度 | 8192 个代币 | 4096 个代币 |
注意力机制 | 分组查询注意力 | 分组查询注意力 |
微调模型 | 是的 | 是的 |
表现 | 在所有基准测试中均优于 Llama 2 | 在大多数基准测试中都优于 Llama 1 |
计算要求 | 非常高(70B 型号) | 非常高(70B 型号) |
可用性 | 开源 | 开源 |
从人类反馈中强化学习 | 是的 | 是的 |
支持的语言数量 | 30种语言 | 20种语言 |
适合于 | 最适合要求更高的任务,例如推理、编码和能力测试 | 适合要求更高的任务,例如推理、编码和能力测试 |
在深入编码之前,了解 LLaMA 3 的架构非常重要。为了更好地直观理解,这里有原版 Transformer、LLaMA 2/3 和 Mistral 之间的比较图。
让我们更详细地了解 LLaMA 3 最重要的组件:
在与 LLaMA 2 相同的 LLaMA 3 方法中,使用一种称为 RMSNorm 的技术来标准化每个变压器子层的输入。
想象一下,您正在准备一场大型考试,并且您有一本充满章节的庞大教科书。每章代表一个不同的主题,但有些章节对于理解该主题比其他章节更重要。现在,在深入研究整本教科书之前,您决定评估每一章的重要性。您不想在每一章上花费相同的时间;你想更多地关注关键问题。这就是使用 RMSNorm 的预规范化在 ChatGPT 等大型语言模型 (LLM) 中发挥作用的地方。这就像根据每一章的重要性为其分配权重。对主题至关重要的章节的权重较高,而不太重要的章节的权重较低。
所以,在深入学习之前,你要根据每一章的加权重要性来调整你的学习计划。您可以将更多的时间和精力分配给权重较高的章节,以确保您彻底掌握核心概念。
同样,使用 RMSNorm 进行预规范化可以帮助法学硕士优先考虑文本的哪些部分对于理解上下文和含义更为重要。它为基本元素分配较高的权重,为不太重要的元素分配较低的权重,确保模型将注意力集中在最需要准确理解的地方。有兴趣的读者可以在这里探索RMSNorm的详细实现。
LLaMA 从 PaLM 中汲取灵感,引入了 SwiGLU 激活函数。
想象一下,您是一名老师,试图向学生解释一个复杂的主题。您有一块大白板,您可以在其中写下要点并绘制图表以使事情更加清晰。但有时,你的字迹可能不是很工整,或者你的图表可能画得不完美。这可能会让你的学生更难理解这些材料。
现在,想象一下,如果您有一支魔笔,可以根据每个点的重要性自动调整笔迹的大小和风格。如果某件事确实很重要,那么钢笔就会把它写得更大、更清晰,使其脱颖而出。如果不太重要,笔会写得更小,但仍然清晰可辨。 SwiGLU 就像 ChatGPT 等大型语言模型 (LLM) 的神笔。在生成文本之前,SwiGLU 会根据每个单词或短语与上下文的相关性来调整其重要性。就像魔笔可以调整书写的大小和风格一样,SwiGLU 可以调整每个单词或短语的重点。
因此,当法学硕士生成文本时,它可以更加突出重要部分,使它们更加引人注目,并确保它们对文本的整体理解做出更多贡献。通过这种方式,SwiGLU 可以帮助法学硕士生成更清晰、更容易理解的文本,就像魔笔如何帮助您在白板上为学生创建更清晰的解释一样。有关 SwiGLU 的更多详细信息可以在相关论文中找到。
旋转嵌入(RoPE)是 LLaMA 3 中使用的一种位置嵌入。
想象一下,您在教室里,想要为学生分配座位进行小组讨论。通常,您可以将座位排列成行和列,每个学生都有一个固定的位置。然而,在某些情况下,您希望创建更加动态的座位安排,让学生可以更自由地走动和互动。
ROPE 就像一种特殊的座位安排,允许学生旋转和改变位置,同时仍然保持彼此的相对位置。学生现在可以绕圈移动,而不是固定在一个地方,从而实现更流畅的互动。
在这种情况下,每个学生代表文本序列中的一个单词或标记,并且他们的位置对应于他们在序列中的位置。就像 ROPE 允许学生旋转和改变位置一样,ROPE 允许文本序列中单词的位置嵌入根据它们之间的相对位置动态变化。因此,在处理文本时,ROPE 引入了旋转方面,而不是将位置嵌入视为固定和静态,从而允许更灵活的表示来捕获序列中单词之间的动态关系。这种灵活性有助于 ChatGPT 等模型更好地理解和生成自然流动并保持连贯性的文本,类似于动态座位安排如何在课堂上促进更多互动讨论。对数学细节感兴趣的人可以参考 RoPE 论文。
LLaMA 3 使用 OpenAI 引入的 tiktoken 库中的字节对编码 (BPE),而 LLaMA 2 分词器 BPE 基于句子库。它们之间有细微的差别,但是
首先,让我们了解一下BPE到底是什么。
让我们从一个简单的例子开始。假设我们有一个文本语料库,其中包含以下单词:“ab”、“bc”、“bcd”和“cde”。我们首先使用文本语料库中的所有单个字符初始化词汇表,因此我们的初始词汇表是{“a”,“b”,“c”,“d”,“e”}。
接下来,我们计算文本语料库中每个字符的频率。对于我们的示例,频率为:{“a”:1、“b”:3、“c”:3、“d”:2、“e”:1}。
现在,我们开始合并过程。我们重复以下步骤,直到我们的词汇量达到所需的大小:
首先,我们找到最常见的连续字符对。在本例中,最频繁的对是“bc”,频率为 2。然后我们合并该对以创建一个新的子词单元“bc”。合并后,我们更新频率计数以反映新的子词单元。更新频率为{"a": 1, "b": 2, "c": 2, "d": 2, "e": 1, "bc": 2}。我们将新的子词单元“bc”添加到我们的词汇表中,它现在变成了{“a”,“b”,“c”,“d”,“e”,“bc”}。
我们重复这个过程。下一个最常见的对是“cd”。我们合并“cd”以形成新的子词单元“cd”并更新频率计数。更新频率为{"a": 1, "b": 2, "c": 1, "d": 1, "e": 1, "bc": 2, "cd": 2}。我们将“cd”添加到词汇表中,得到{“a”,“b”,“c”,“d”,“e”,“bc”,“cd”}。
继续这个过程,下一个频繁对是“de”。我们合并“de”以形成子词单元“de”并将频率计数更新为{“a”:1,“b”:2,“c”:1,“d”:1,“e”:0, “bc”:2,“cd”:1,“de”:1}。我们将“de”添加到词汇表中,使其成为{“a”,“b”,“c”,“d”,“e”,“bc”,“cd”,“de”}。
接下来,我们发现“ab”是最常见的对。我们合并“ab”以形成子词单元“ab”并将频率计数更新为{“a”:0,“b”:1,“c”:1,“d”:1,“e”:0, “bc”:2,“cd”:1,“de”:1,“ab”:1}。
我们将“ab”添加到词汇表中,它变成{“a”,“b”,“c”,“d”,“e”,“bc”,“cd”,“de”,“ab”}。
那么,下一个频繁对是“bcd”。我们合并“bcd”以形成子词单元“bcd”,并将频率计数更新为{“a”:0,“b”:0,“c”:0,“d”:0,“e”:0, “bc”:1,“cd”:0,“de”:1,“ab”:1,“bcd”:1}。我们将“bcd”添加到词汇表中,结果是{“a”,“b”,“c”,“d”,“e”,“bc”,“cd”,“de”,“ab”,“bcd” “}。
最后,最常见的对是“cde”。我们合并“cde”以形成子词单元“cde”并将频率计数更新为{“a”:0,“b”:0,“c”:0,“d”:0,“e”:0, “bc”:1,“cd”:0,“de”:0,“ab”:1,“bcd”:1,“cde”:1}。我们将“cde”添加到词汇表中,使其成为{“a”,“b”,“c”,“d”,“e”,“bc”,“cd”,“de”,“ab”,“bcd” ”,“cde”}。
该技术可以提高法学硕士的表现并处理罕见的和词汇外的单词。 TikToken BPE 和句子 BPE 之间的最大区别在于,如果整个单词已知,TikToken BPE 并不总是将单词分割成更小的部分。例如,如果词汇表中有“hugging”,它会保留为一个标记,而不是拆分为 [“hug”,“ging”]。
我们将使用一小部分 Python 库,但最好安装它们以避免遇到“未找到模块”错误。
!p ip install sentencepiece tiktoken torch blobfile matplotlib huggingface_hub
Requirement already satisfied: sentencepiece in /opt/conda/lib/python3.10/site-packages (0.2.0)
Requirement already satisfied: tiktoken in /opt/conda/lib/python3.10/site-packages (0.7.0)
Requirement already satisfied: torch in /opt/conda/lib/python3.10/site-packages (2.1.2+cpu)
Requirement already satisfied: blobfile in /opt/conda/lib/python3.10/site-packages (2.1.1)
Requirement already satisfied: matplotlib in /opt/conda/lib/python3.10/site-packages (3.7.5)
Requirement already satisfied: huggingface_hub in /opt/conda/lib/python3.10/site-packages (0.22.2)
Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.10/site-packages (from tiktoken) (2023.12.25)
Requirement already satisfied: requests>=2.26.0 in /opt/conda/lib/python3.10/site-packages (from tiktoken) (2.31.0)
Requirement already satisfied: filelock in /opt/conda/lib/python3.10/site-packages (from torch) (3.13.1)
Requirement already satisfied: typing-extensions in /opt/conda/lib/python3.10/site-packages (from torch) (4.9.0)
Requirement already satisfied: sympy in /opt/conda/lib/python3.10/site-packages (from torch) (1.12)
Requirement already satisfied: networkx in /opt/conda/lib/python3.10/site-packages (from torch) (3.2.1)
Requirement already satisfied: jinja2 in /opt/conda/lib/python3.10/site-packages (from torch) (3.1.2)
Requirement already satisfied: fsspec in /opt/conda/lib/python3.10/site-packages (from torch) (2024.2.0)
Requirement already satisfied: pycryptodomex~=3.8 in /opt/conda/lib/python3.10/site-packages (from blobfile) (3.20.0)
Requirement already satisfied: urllib3<3,>=1.25.3 in /opt/conda/lib/python3.10/site-packages (from blobfile) (1.26.18)
Requirement already satisfied: lxml~=4.9 in /opt/conda/lib/python3.10/site-packages (from blobfile) (4.9.4)
Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (4.47.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.4.5)
Requirement already satisfied: numpy<2,>=1.20 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.26.4)
Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (21.3)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (9.5.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (3.1.1)
Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.10/site-packages (from huggingface_hub) (6.0.1)
Requirement already satisfied: tqdm>=4.42.1 in /opt/conda/lib/python3.10/site-packages (from huggingface_hub) (4.66.1)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (3.6)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (2024.2.2)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.10/site-packages (from jinja2->torch) (2.1.3)
Requirement already satisfied: mpmath>=0.19 in /opt/conda/lib/python3.10/site-packages (from sympy->torch) (1.3.0)
安装所需的库后,我们需要下载一些文件。由于我们要复制 llama-3–8B 的架构,因此您必须在 HuggingFace 上有一个帐户。此外,由于 llama-3 是一个门控模型,您必须接受其条款和条件才能访问模型内容。
步骤如下:
完成这两个步骤后,现在我们必须下载一些文件。有两种选择可以做到这一点:
(选项 1:手动)从此链接转到 llama-3–8B HF 目录,然后手动下载这三个文件。
(选项 2:编码)我们可以使用之前安装的 Hugging_face 库来下载所有这些文件。然而,首先,我们需要使用 HF 令牌在工作笔记本中登录 HuggingFace Hub。您可以创建新令牌或从此链接访问它。
# Import the `notebook_login` function from the `huggingface_hub` module.
from huggingface_hub import notebook_login
# Execute the `notebook_login` function to log in to the Hugging Face Hub.
notebook_login ()
VBox(children=(HTML(value='<center> <imgnsrc=https://huggingface.co/front/assets/huggingface_logo-noborder.sv…
运行此单元后,它会要求您输入令牌。如果登录期间出现错误,请重试,但请确保取消选中添加令牌作为 git 凭证。之后,我们只需要运行一个简单的 Python 代码即可下载作为 llama-3-8B 架构骨干的三个文件。
# Import the necessary function from the huggingface_hub library
from huggingface_hub import hf_hub_download
# Define the repository information
repo_id = "meta-llama/Meta-Llama-3-8B"
subfolder = "original" # Specify the subfolder within the repository
# List of filenames to download
filenames = [ "params.json" , "tokenizer.model" , "consolidated.00.pth" ]
# Specify the directory where you want to save the downloaded files
save_directory = "llama-3-8B/" # Replace with your desired path
# Download each file
for filename in filenames :
hf_hub_download (
repo_id = repo_id , # Repository ID
filename = filename , # Name of the file to download
subfolder = subfolder , # Subfolder within the repository
local_dir = save_directory # Directory to save the downloaded file
)
original/params.json: 0%| | 0.00/211 [00:00<?, ?B/s]
original/tokenizer.model: 0%| | 0.00/2.18M [00:00<?, ?B/s]
original/consolidated.00.pth: 0%| | 0.00/16.1G [00:00<?, ?B/s]
下载所有文件后,我们需要导入将在本博客中使用的库。
# File system paths
from pathlib import Path
# Tokenization library
import tiktoken
# BPE loading function
from tiktoken . load import load_tiktoken_bpe
# PyTorch library
import torch
# JSON handling
import json
# Plotting library
import matplotlib . pyplot as plt
接下来,我们需要了解每个文件的用途。
由于我们的目标是精确复制 llama-3,这意味着我们的输入文本必须产生有意义的输出。例如,如果我们的输入是“太阳的颜色是?”,则输出必须是“白色”。实现这一目标需要在大型数据集上训练我们的法学硕士,这需要很高的计算能力,这对我们来说是不可行的。
然而,Meta 已经公开发布了他们的 llama-3 架构文件,或者更复杂地说,他们的预训练权重,以供使用。我们刚刚下载了这些文件,使我们能够复制它们的架构,而无需训练或大型数据集。一切都已经准备好了,我们只需在正确的地方使用正确的组件即可。
查看每个文件及其重要性:
tokenizer.model - 正如我们之前讨论的,LLaMA-3 使用 tiktoken 的字节对编码 (BPE) 标记器,在包含 15 万亿个标记的数据集上进行训练 - 比 LLaMA-2 使用的数据集大 7 倍。让我们加载这个文件并看看它包含什么。
# Loading the tokenizer from llama-3-8B
tokenizer_model = load_tiktoken_bpe ( "/kaggle/working/llama-3-8B/original/tokenizer.model" )
# Get the length of the tokenizer model
len ( tokenizer_model )
# OUTPUT: 128000
# Get the type of the `tokenizer_model` object.
type ( tokenizer_model )
# OUTPUT: dictionary
dict
长度属性显示总词汇量大小,即训练数据中唯一的字符数。 tokenizer_model 的类型是字典。
# Printing the first 10 items of tokenizer model
dict ( list ( tokenizer_model . items ())[ 5600 : 5610 ])
{b'mitted': 5600,
b" $('#": 5601,
b' saw': 5602,
b' approach': 5603,
b'ICE': 5604,
b' saying': 5605,
b' anyone': 5606,
b'meta': 5607,
b'SD': 5608,
b' song': 5609}
当我们从中打印 10 个随机项目时,您将看到使用 BPE 算法形成的字符串,类似于我们之前讨论的示例。键表示来自 BPE 训练的字节序列,而值表示基于频率的合并排名。
solidated.00.pth - 包含 Llama-3–8B 的学习参数(权重)。这些参数包括有关模型如何理解和处理语言的信息,例如如何表示标记、计算注意力、执行前馈变换以及标准化其输出。
# Loading a PyTorch model of LLaMA-3-8B
model = torch . load ( "/kaggle/working/llama-3-8B/original/consolidated.00.pth" )
# printing first 11 layers of the architecture
list ( model . keys ())[: 11 ]
['tok_embeddings.weight',
'layers.0.attention.wq.weight',
'layers.0.attention.wk.weight',
'layers.0.attention.wv.weight',
'layers.0.attention.wo.weight',
'layers.0.feed_forward.w1.weight',
'layers.0.feed_forward.w3.weight',
'layers.0.feed_forward.w2.weight',
'layers.0.attention_norm.weight',
'layers.0.ffn_norm.weight',
'layers.1.attention.wq.weight']
如果您熟悉转换器架构,您就会了解查询、键矩阵等。稍后,我们将使用这些层/权重在 Llama-3 的架构中创建此类矩阵。
params.json-包含各种参数值,例如:
# Opening the parameters JSON file
with open ( "/kaggle/working/llama-3-8B/original/params.json" , "r" ) as f :
config = json . load ( f )
# Printing the content
print ( config )
{'dim': 4096, 'n_layers': 32, 'n_heads': 32, 'n_kv_heads': 8, 'vocab_size': 128256, 'multiple_of': 1024, 'ffn_dim_multiplier': 1.3, 'norm_eps': 1e-05, 'rope_theta': 500000.0}
这些值将帮助我们通过指定头部数量、嵌入向量的维度等细节来复制 Llama-3 架构。
让我们存储这些值,以便稍后使用它们。
# Dimension
dim = config [ "dim" ]
# Layers
n_layers = config [ "n_layers" ]
# Heads
n_heads = config [ "n_heads" ]
# KV_heads
n_kv_heads = config [ "n_kv_heads" ]
# Vocabulary
vocab_size = config [ "vocab_size" ]
# Multiple
multiple_of = config [ "multiple_of" ]
# Multiplier
ffn_dim_multiplier = config [ "ffn_dim_multiplier" ]
# Epsilon
norm_eps = config [ "norm_eps" ]
# RoPE
rope_theta = torch . tensor ( config [ "rope_theta" ])
现在我们有了分词器模型、包含权重的架构模型和配置参数,让我们开始从头开始编写我们自己的 Llama-3。
我们需要执行的第一件事是将输入文本转换为标记,为了实现这一点,我们首先必须创建一些特殊的标记,这些标记是在标记化文本中提供结构化标记所必需的,使标记生成器能够识别和处理特定条件或说明。
special_tokens = [
"<|begin_of_text|>" , # Marks the beginning of a text sequence.
"<|end_of_text|>" , # Marks the end of a text sequence.
"<|reserved_special_token_0|>" , # Reserved for future use.
"<|reserved_special_token_1|>" , # Reserved for future use.
"<|reserved_special_token_2|>" , # Reserved for future use.
"<|reserved_special_token_3|>" , # Reserved for future use.
"<|start_header_id|>" , # Indicates the start of a header ID.
"<|end_header_id|>" , # Indicates the end of a header ID.
"<|reserved_special_token_4|>" , # Reserved for future use.
"<|eot_id|>" , # Marks the end of a turn (in a conversational context).
] + [ f"<|reserved_special_token_ { i } |>" for i in range ( 5 , 256 - 5 )] # A large set of tokens reserved for future use.
接下来,我们通过指定不同的模式来匹配输入文本中的各种类型的子字符串,从而定义将文本拆分为标记的规则。我们可以这样做。
# patterns based on which text will be break into tokens
tokenize_breaker = r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^rnp{L}p{N}]?p{L}+|p{N}{1,3}| ?[^sp{L}p{N}]+[rn]*|s*[rn]+|s+(?!S)|s+"
它可以从输入文本中提取单词、缩写、数字(最多三位)和非空白字符序列,您可以根据您的要求进行自定义。我们需要使用 TikToken BPE 编写一个简单的分词器函数,该函数需要三个输入:tokenizer_model、tokenize_breaker 和special_tokens。该函数将相应地对我们的输入文本进行编码/解码。
# Initialize tokenizer with specified parameters
tokenizer = tiktoken . Encoding (
# make sure to set path to tokenizer.model file
name = "/kaggle/working/llama-3-8B/original/tokenizer.model" ,
# Define tokenization pattern string
pat_str = tokenize_breaker ,
# Assign BPE mergeable ranks from tokenizer_model of LLaMA-3
mergeable_ranks = tokenizer_model ,
# Set special tokens with indices
special_tokens = { token : len ( tokenizer_model ) + i for i , token in enumerate ( special_tokens )},
)
# Encode "hello world!" and decode tokens to string
tokenizer . decode ( tokenizer . encode ( "hello world!" ))
'hello world!'
为了验证我们的编码器函数方法是否正常工作,我们将“Hello World”传递给它。首先,它对文本进行编码,将其转换为数值。然后,它将其解码回文本,结果是“hello world!”。这证实了该功能正常工作。让我们对输入进行标记。
# input prompt
prompt = "the answer to the ultimate question of life, the universe, and everything is "
# Encode the prompt using the tokenizer and prepend a special token (128000)
tokens = [ 128000 ] + tokenizer . encode ( prompt )
print ( tokens ) # Print the encoded tokens
# Convert the list of tokens into a PyTorch tensor
tokens = torch . tensor ( tokens )
# Decode each token back into its corresponding string
prompt_split_as_tokens = [ tokenizer . decode ([ token . item ()]) for token in tokens ]
print ( prompt_split_as_tokens ) # Print the decoded tokens
[128000, 1820, 4320, 311, 279, 17139, 3488, 315, 2324, 11, 279, 15861, 11, 323, 4395, 374, 220]
['<|begin_of_text|>', 'the', ' answer', ' to', ' the', ' ultimate', ' question', ' of', ' life', ',', ' the', ' universe', ',', ' and', ' everything', ' is', ' ']
我们以一个特殊的标记开始对输入文本“生命、宇宙和一切的终极问题的答案是”进行编码。
如果我们检查输入向量的长度,它将是:
# checking dimension of input vector and embedding vector from llama-3 architecture
print ( dim , len ( tokens ))
4096 17
我们的输入向量目前的尺寸为 (17x1),需要将其转换为每个标记化单词的嵌入。这意味着我们的 (17x1) 令牌将变为 (17x4096),其中每个令牌都有长度为 4096 的相应嵌入。
# Define embedding layer with vocab size and embedding dimension
embedding_layer = torch . nn . Embedding ( vocab_size , dim )
# Copy pre-trained token embeddings to the embedding layer
embedding_layer . weight . data . copy_ ( model [ "tok_embeddings.weight" ])
# Get token embeddings for given tokens, converting to torch.bfloat16 format
token_embeddings_unnormalized = embedding_layer ( tokens ). to ( torch . bfloat16 )
# Print shape of resulting token embeddings
token_embeddings_unnormalized . shape
torch.Size([17, 4096])
这些嵌入没有标准化,如果我们不对它们进行标准化,将会产生严重的影响。在下一节中,我们将对输入向量进行归一化。
我们将使用我们之前看到的 RMSNorm 相同公式对输入向量进行归一化,以确保我们的输入得到归一化。
# Calculating RMSNorm
def rms_norm ( tensor , norm_weights ):
# Calculate the mean of the square of tensor values along the last dimension
squared_mean = tensor . pow ( 2 ). mean ( - 1 , keepdim = True )
# Add a small value to avoid division by zero
normalized = torch . rsqrt ( squared_mean + norm_eps )
# Multiply normalized tensor by the provided normalization weights
return ( tensor * normalized ) * norm_weights
我们将使用 Layers_0 的注意力权重来标准化我们的非标准化嵌入。使用 Layer_0 的原因是我们现在正在创建 LLaMA-3 变压器架构的第一层。
# using RMS normalization and provided normalization weights
token_embeddings = rms_norm ( token_embeddings_unnormalized ,
model [ "layers.0.attention_norm.weight" ])
# Print the shape of the resulting token embeddings
token_embeddings . shape
torch.Size([17, 4096])
您可能已经知道维度不会改变,因为我们只是标准化向量而没有其他。
首先,我们从模型中加载查询、键、值和输出向量。
# Print the shapes of different weights
print (
# Query weight shape
model [ "layers.0.attention.wq.weight" ]. shape ,
# Key weight shape
model [ "layers.0.attention.wk.weight" ]. shape ,
# Value weight shape
model [ "layers.0.attention.wv.weight" ]. shape ,
# Output weight shape
model [ "layers.0.attention.wo.weight" ]. shape
)
torch.Size([4096, 4096]) torch.Size([1024, 4096]) torch.Size([1024, 4096]) torch.Size([4096, 4096])
这些维度表明,由于实施并行方法/训练,我们下载的模型权重不是针对每个头的,而是针对多个注意力头的。但是,我们可以解开这些矩阵,使其仅可用于单个头。
# Retrieve query weight for the first layer of attention
q_layer0 = model [ "layers.0.attention.wq.weight" ]
# Calculate dimension per head
head_dim = q_layer0 . shape [ 0 ] // n_heads
# Reshape query weight to separate heads
q_layer0 = q_layer0 . view ( n_heads , head_dim , dim )
# Print the shape of the reshaped query weight tensor
q_layer0 . shape
torch.Size([32, 128, 4096])
这里,32是Llama-3中注意力头的数量,128是查询向量的大小,4096是令牌嵌入的大小。我们可以使用以下方法访问第一层第一个头的查询权重矩阵:
# Extract the query weight for the first head of the first layer of attention
q_layer0_head0 = q_layer0 [ 0 ]
# Print the shape of the extracted query weight tensor for the first head
q_layer0_head0 . shape
torch.Size([128, 4096])
为了找到每个标记的查询向量,我们将查询权重与标记嵌入相乘。
# Matrix multiplication: token embeddings with transpose of query weight for first head
q_per_token = torch . matmul ( token_embeddings , q_layer0_head0 . T )
# Shape of resulting tensor: queries per token
q_per_token . shape
torch.Size([17, 128])
查询向量本身并不知道它们在提示中的位置,因此我们将使用 RoPE 来让它们知道这一点。
我们spl