>  기사  >  기술 주변기기  >  손으로 찢기 Llama3 레이어 1: 처음부터 llama3 구현

손으로 찢기 Llama3 레이어 1: 처음부터 llama3 구현

WBOY
WBOY원래의
2024-06-01 17:45:42976검색

1. Llama3의 아키텍처

이 기사 시리즈에서는 llama3를 처음부터 구현합니다.

Llama3의 전체 아키텍처:

手撕Llama3第1层: 从零开始实现llama3그림

Llama3의 모델 매개변수:

LlaMa 3 모델에서 이러한 매개변수의 실제 값을 살펴보겠습니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

[1] 컨텍스트 창(context-window)

LlaMa 클래스를 인스턴스화할 때 max_seq_len 변수는 컨텍스트 창을 정의합니다. 클래스에는 다른 매개변수도 있지만 이 매개변수는 변환기 모델과 가장 직접적으로 관련됩니다. 여기서 max_seq_len은 8K입니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

[2] Vocabulary-size and Attention Layers

Transformer 클래스는 어휘와 레이어 수를 정의하는 모델입니다. 여기서 어휘는 모델이 인식하고 처리할 수 있는 단어(및 토큰) 세트를 나타냅니다. Attention 레이어는 모델에 사용된 변환기 블록(attention 및 피드포워드 레이어의 조합)을 나타냅니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

이 숫자에 따르면 LlaMa 3의 어휘는 128K로 상당히 많습니다. 또한 32개의 변압기 블록이 있습니다.

[3] 기능 차원 및 Attention 헤드

Feature 차원 및 Attention 헤드가 Self-Attention 모듈에 도입되었습니다. 특징 차원은 임베딩 공간에 있는 토큰의 벡터 크기를 나타내며(특징 차원은 입력 데이터 또는 임베딩 벡터의 차원 크기를 나타냄) Attention-heads에는 변환기에서 self-attention 메커니즘을 구동하는 QK 모듈이 포함됩니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

[4] Hidden Dimensions

Hidden Dimensions은 피드포워드 신경망(Feed Forward)에서 히든 레이어의 차원 크기를 말합니다. 피드포워드 신경망에는 일반적으로 하나 이상의 숨겨진 레이어가 포함되며 이러한 숨겨진 레이어의 크기에 따라 네트워크의 용량과 복잡성이 결정됩니다. Transformer 모델에서 피드포워드 신경망의 은닉층 차원은 일반적으로 모델의 표현 능력을 높이기 위해 특징 차원의 배수입니다. LLama3에서 숨겨진 차원은 기능 차원의 1.3배입니다. 은닉층과 은닉차원은 두 가지 개념이라는 점에 유의해야 합니다.

숨겨진 레이어 수가 많을수록 네트워크는 더 작은 출력 크기로 다시 투영하기 전에 더 풍부한 표현을 내부적으로 생성하고 조작할 수 있습니다.

手撕Llama3第1层: 从零开始实现llama3Picture

[5] 위의 매개변수를 Transformer로 결합합니다

첫 번째 행렬은 Attention Weighted 특성을 생성하기 위해 Attention 레이어에서 처리되는 입력 특성 행렬입니다. 이 이미지에서 입력 특성 행렬의 크기는 5 x 3에 불과하지만 실제 Llama 3 모델에서는 8K x 4096으로 커져 엄청납니다.

다음은 피드포워드 네트워크의 숨겨진 레이어로, 마지막 레이어에서 5325까지 성장했다가 다시 4096으로 감소합니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

[6] 여러 레이어의 변압기 블록

LlaMa 3은 위의 ​​32개의 변압기 블록을 결합하고 출력은 마지막 블록에 도달할 때까지 한 블록에서 다음 블록으로 전달됩니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

[7] 모두 합치기

위의 모든 부분이 시작되면 이제 이 부분들을 하나로 모아서 LlaMa 효과가 어떻게 만들어지는지 살펴보겠습니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

1단계: 먼저 크기가 8K(컨텍스트 창) x 128K(어휘 크기)인 입력 행렬이 있습니다. 이 행렬은 고차원 행렬을 저차원 행렬로 변환하기 위해 임베딩 과정을 거칩니다.

2단계: 이 경우 이 저차원 결과는 4096이 됩니다. 이는 앞서 본 LlaMa 모델의 특징에 대한 지정된 차원입니다.

신경망에서는 차원 강화와 차원 축소가 일반적인 작업이며 각각 다른 목적과 효과를 가지고 있습니다.

차원성 강화는 일반적으로 더 복잡한 특징과 패턴을 포착할 수 있도록 모델의 용량을 늘리기 위해 수행됩니다. 입력 데이터가 더 높은 차원 공간에 매핑되면 모델에서 다양한 기능 조합을 더 쉽게 구분할 수 있습니다. 이는 모델이 더 복잡한 결정 경계를 학습하는 데 도움이 되므로 비선형 문제를 처리할 때 특히 유용합니다.

차원성 감소는 모델의 복잡성과 과적합 위험을 줄이는 것입니다. 특징 공간의 차원을 줄임으로써 모델은 더 세련되고 일반화된 특징 표현을 학습하도록 강제할 수 있습니다. 또한, 차원 축소는 모델의 일반화 능력을 향상시키는 데 도움이 되는 정규화 방법으로 사용될 수 있습니다. 경우에 따라 차원 축소는 계산 비용을 줄이고 모델 운영 효율성을 향상시킬 수도 있습니다.

실제 응용에서는 차원 증가 전략과 차원 축소 전략을 특징 추출 및 변환 과정으로 간주할 수 있습니다. 이 과정에서 모델은 먼저 차원을 높여 데이터의 본질적인 구조를 탐색한 다음 차원을 줄여 가장 유용한 특징과 패턴을 추출합니다. 이 접근 방식은 모델이 충분한 복잡성을 유지하면서 훈련 데이터에 대한 과적합을 방지하는 데 도움이 될 수 있습니다.

3단계: 이 기능은 Transformer 블록을 통해 먼저 Attention 레이어, 그 다음 FFN 레이어에서 처리됩니다. Attention 레이어는 수평으로 기능을 처리하는 반면, FFN 레이어는 수직으로 차원을 처리합니다.

4단계: 트랜스포머 블록의 32개 레이어에 대해 3단계가 반복됩니다. 마지막으로 결과 행렬의 차원은 특징 차원에 사용된 차원과 동일합니다.

5단계: 마지막으로 이 행렬은 원래 어휘 행렬 크기인 128K로 다시 변환되므로 모델은 어휘에서 사용 가능한 단어를 선택하고 매핑할 수 있습니다.

이것이 LlaMa 3가 해당 벤치마크에서 높은 점수를 얻고 LlaMa 3 효과를 생성하는 방법입니다.

쉽게 혼동하기 쉬운 몇 가지 용어를 간략하게 요약하겠습니다.

1.max_seq_len(최대 시퀀스 길이)

이것은 모델이 단일 처리에서 허용할 수 있는 최대 토큰 수입니다.

LlaMa 3-8B 모델에서는 이 매개변수가 8,000 토큰, 즉 Context Window Size = 8K로 설정됩니다. 이는 모델이 단일 처리에서 고려할 수 있는 최대 토큰 수가 8,000개임을 의미합니다. 이는 긴 텍스트를 이해하거나 장기적인 대화의 맥락을 유지하는 데 중요합니다.

2. Vocabulary-size(어휘)

모델이 인식할 수 있는 다양한 토큰의 개수입니다. 여기에는 가능한 모든 단어, 구두점 및 특수 문자가 포함됩니다. 모델의 어휘는 128,000개이며 어휘 크기 = 128K로 표현됩니다. 이는 모델이 다양한 단어, 문장 부호 및 특수 문자를 포함하는 128,000개의 다양한 토큰을 인식하고 처리할 수 있음을 의미합니다.

3. 주의 레이어

Transformer 모델의 주요 구성 요소입니다. 입력 데이터의 어느 부분이 가장 중요한지(즉, 어떤 토큰이 "참석"되는지) 학습하여 입력 데이터 처리를 주로 담당합니다. 모델에는 이러한 레이어가 여러 개 있을 수 있으며, 각 레이어는 서로 다른 관점에서 입력 데이터를 이해하려고 합니다.

LlaMa 3-8B 모델에는 32개의 처리 레이어가 포함되어 있습니다. 즉, 레이어 수 = 32입니다. 이러한 계층에는 여러 주의 계층과 기타 유형의 네트워크 계층이 포함되며, 각 계층은 서로 다른 관점에서 입력 데이터를 처리하고 이해합니다.

4. 변압기 블록

일반적으로 하나 이상의 주의 계층과 피드포워드 네트워크를 포함하는 다양한 계층의 모듈을 포함합니다. 모델에는 여러 개의 변압기 블록이 있을 수 있습니다. 이러한 블록은 순차적으로 연결되며 각 블록의 출력은 다음 블록의 입력이 됩니다. 트랜스포머 블록은 디코더 레이어라고도 불릴 수 있습니다.

Transformer 모델의 맥락에서 일반적으로 모델에 "32개의 레이어"가 있다고 말합니다. 이는 모델에 "32개의 Transformer 블록"이 있다고 말하는 것과 동일할 수 있습니다. 각 Transformer 블록은 일반적으로 self-attention 계층과 피드포워드 신경망 계층을 포함합니다. 이 두 하위 계층은 함께 완전한 처리 단위 또는 "계층"을 형성합니다.

따라서 모델에 32개의 Transformer 블록이 있다고 말할 때 실제로는 모델이 32개의 처리 장치로 구성되어 있으며 각 처리 장치는 셀프 어텐션 처리 및 데이터 피드포워드 네트워크 처리가 가능하다는 것을 설명하는 것입니다. 이 프레젠테이션에서는 모델의 계층 구조와 각 수준에서의 처리 기능을 강조합니다.

요약하자면, "32개 레이어"와 "32개 변환기 블록"은 Transformer 모델 구조를 설명할 때 기본적으로 동의어입니다. 둘 다 모델에 self-attention 및 Feedforward 네트워크 작업이 포함된 32개의 독립적인 데이터 처리 주기가 포함되어 있음을 의미합니다.

5. Feature-dimension

모델에서 입력 토큰을 벡터로 표현했을 때 각 벡터의 차원입니다.

각 토큰은 모델의 4096개 특징을 포함하는 벡터, 즉 Feature-dimension = 4096으로 변환됩니다. 이러한 높은 차원을 통해 모델은 보다 풍부한 의미 정보와 상황별 관계를 포착할 수 있습니다.

6. Attention-Heads

각 Attention-Heads에는 여러 개의 Attention-Heads가 있을 수 있으며, 각 헤드는 서로 다른 관점에서 입력 데이터를 독립적으로 분석합니다.

각 주의 레이어에는 32개의 독립적인 주의 헤드가 포함되어 있습니다. 즉, 주의 헤드 수 = 32입니다. 이러한 헤드는 다양한 측면에서 입력 데이터를 분석하고 보다 포괄적인 데이터 분석 기능을 공동으로 제공합니다.

7. 숨겨진 차원

이것은 일반적으로 피드포워드 네트워크의 레이어 너비, 즉 각 레이어의 뉴런 수를 나타냅니다. 일반적으로 숨겨진 차원은 기능 차원보다 크므로 모델이 내부적으로 더 풍부한 데이터 표현을 생성할 수 있습니다.

피드포워드 네트워크에서 히든 레이어의 차원은 5325, 즉 히든 차원 = 5325입니다. 이는 기능 차원보다 크므로 모델이 내부 레이어 간에 더 깊은 기능 변환 및 학습을 수행할 수 있습니다.

관계 및 값:

주의 계층과 주의 머리 사이의 관계: 각 주의 계층에는 여러 주의 머리가 포함될 수 있습니다.

수치적 관계: 모델에는 여러 개의 변환기 블록이 있을 수 있으며, 각 블록에는 주의 레이어와 하나 이상의 다른 레이어가 포함되어 있습니다. 각 주의 계층에는 여러 개의 주의 헤드가 있을 수 있습니다. 이러한 방식으로 전체 모델은 다양한 레이어와 헤드에서 복잡한 데이터 처리를 수행합니다.

Llama3 모델의 공식 링크 스크립트를 다운로드하세요: https://llama.meta.com/llama-downloads/

2. 모델 보기

다음 코드는 tiktoken 라이브러리를 사용하여 로드하고 사용하는 방법을 보여줍니다. BPE(바이트 쌍 기반 인코딩) 토크나이저. 이 토크나이저는 특히 자연어 처리 및 기계 학습 모델에 사용하기 위해 텍스트 데이터를 처리하도록 설계되었습니다.

Hello World에 들어가서 단어 분할기가 어떻게 단어 분할을 수행하는지 살펴보겠습니다.

from pathlib import Pathimport tiktokenfrom tiktoken.load import load_tiktoken_bpeimport torchimport jsonimport matplotlib.pyplot as plttokenizer_path = "Meta-Llama-3-8B/tokenizer.model"special_tokens = ["<|begin_of_text|>","<|end_of_text|>","<|reserved_special_token_0|>","<|reserved_special_token_1|>","<|reserved_special_token_2|>","<|reserved_special_token_3|>","<|start_header_id|>","<|end_header_id|>","<|reserved_special_token_4|>","<|eot_id|>",# end of turn] + [f"<|reserved_special_token_{i}|>" for i in range(5, 256 - 5)]mergeable_ranks = load_tiktoken_bpe(tokenizer_path)tokenizer = tiktoken.Encoding(name=Path(tokenizer_path).name,pat_str=r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+",mergeable_ranks=mergeable_ranks,special_tokens={token: len(mergeable_ranks) + i for i, token in enumerate(special_tokens)},)tokenizer.decode(tokenizer.encode("hello world!"))

手撕Llama3第1层: 从零开始实现llama3Pictures

모델 파일 읽기


手撕Llama3第1层: 从零开始实现llama3

로드된 모델 파일에 포함된 처음 20개 매개변수 또는 가중치의 이름을 봅니다.

model = torch.load("Meta-Llama-3-8B/consolidated.00.pth")print(json.dumps(list(model.keys())[:20], indent=4))

手撕Llama3第1层: 从零开始实现llama3Pictures

  1. "tok_embeddings.weight": 모델에 입력 단어(또는 더 일반적으로는 토큰)를 고정 차원 벡터로 변환하는 단어 임베딩 레이어가 있음을 나타냅니다. 이는 대부분의 자연어 처리 모델의 첫 번째 단계입니다.
  2. "layers.0.attention..." 및 "layers.1.attention...": 이러한 매개변수는 여러 레이어를 나타내며 각 레이어에는 주의 메커니즘 모듈이 포함되어 있습니다. 이 모듈에서 wq, wk, wv 및 wo는 각각 쿼리, 키, 값 및 출력의 가중치 행렬을 나타냅니다. 이는 Transformer 모델의 핵심 구성 요소이며 입력 시퀀스의 여러 부분 간의 관계를 캡처하는 데 사용됩니다.
  3. "layers.0.feed_forward..." 및 "layers.1.feed_forward...": 이러한 매개변수는 각 계층에 일반적으로 두 개의 선형 변환으로 구성된 피드포워드 네트워크(Feed Forward Network)도 포함되어 있음을 나타냅니다. 중간에 비선형 활성화 함수가 있습니다. w1, w2, w3은 이 피드포워드 네트워크에서 서로 다른 선형 레이어의 가중치를 나타낼 수 있습니다.
  4. "layers.0.attention_norm.weight" 및 "layers.1.attention_norm.weight": 이 매개변수는 안정화 훈련 프로세스를 위해 각 계층의 주의 모듈 뒤에 정규화 계층(아마도 계층 정규화)이 있음을 나타냅니다.
  5. "layers.0.ffn_norm.weight" 및 "layers.1.ffn_norm.weight": 이 매개변수는 피드포워드 네트워크 뒤에 정규화 계층도 있음을 나타냅니다. 위 코드의 출력 내용은 Llama3의 Transformer Block인 아래 그림과 같습니다.

手撕Llama3第1层: 从零开始实现llama3Pictures

전반적으로 이 출력은 Transformer 아키텍처를 기반으로 하는 딥 러닝 모델의 주요 구성 요소를 보여줍니다. 이 모델은 텍스트 분류, 기계 번역, 질문 응답 시스템 등과 같은 자연어 처리 작업에 널리 사용됩니다. 모델이 복잡한 입력 시퀀스 기능을 포착하는 데 도움이 되는 주의 메커니즘, 피드포워드 네트워크 및 정규화 레이어를 포함하여 각 레이어의 구조는 거의 동일합니다.

Llama3 모델의 매개변수 구성 보기:

with open("Meta-Llama-3-8B/params.json", "r") as f:config = json.load(f)config

手撕Llama3第1层: 从零开始实现llama3图片

  1. 'dim': 4096 - 表示模型中的隐藏层维度或特征维度。这是模型处理数据时每个向量的大小。
  2. 'n_layers': 32 - 表示模型中层的数量。在基于Transformer的模型中,这通常指的是编码器和解码器中的层的数量。
  3. 'n_heads': 32 - 表示在自注意力(Self-Attention)机制中,头(head)的数量。多头注意力机制是Transformer模型的关键特性之一,它允许模型在不同的表示子空间中并行捕获信息。
  4. 'n_kv_heads': 8 - 这个参数不是标准Transformer模型的常见配置,可能指的是在某些特定的注意力机制中,用于键(Key)和值(Value)的头的数量。
  5. 'vocab_size': 128256 - 表示模型使用的词汇表大小。这是模型能够识别的不同单词或标记的总数。
  6. 'multiple_of': 1024 - 这可能是指模型的某些维度需要是1024的倍数,以确保模型结构的对齐或优化。
  7. 'ffn_dim_multiplier': 1.3 - 表示前馈网络(Feed-Forward Network, FFN)的维度乘数。在Transformer模型中,FFN是每个注意力层后的一个网络,这个乘数可能用于调整FFN的大小。
  8. 'norm_eps': 1e-05 - 表示在归一化层(如Layer Normalization)中使用的epsilon值,用于防止除以零的错误。这是数值稳定性的一个小技巧。
  9. 'rope_theta': 500000.0 - 这个参数不是标准Transformer模型的常见配置,可能是指某种特定于模型的技术或优化的参数。它可能与位置编码或某种正则化技术有关。

我们使用这个配置来推断模型的细节,比如:

  1. 模型有32个Transformer层
  2. 每个多头注意力块有32个头
  3. 词汇表的大小等等 
dim = config["dim"]n_layers = config["n_layers"]n_heads = config["n_heads"]n_kv_heads = config["n_kv_heads"]vocab_size = config["vocab_size"]multiple_of = config["multiple_of"]ffn_dim_multiplier = config["ffn_dim_multiplier"]norm_eps = config["norm_eps"]rope_theta = torch.tensor(config["rope_theta"])

手撕Llama3第1层: 从零开始实现llama3图片

将Text转化为Token

代码如下:

prompt = "the answer to the ultimate question of life, the universe, and everything is "tokens = [128000] + tokenizer.encode(prompt)print(tokens)tokens = torch.tensor(tokens)prompt_split_as_tokens = [tokenizer.decode([token.item()]) for token in tokens]print(prompt_split_as_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', ' ']

将令牌转换为它们的嵌入表示

截止到目前,我们的[17x1]令牌现在变成了[17x4096],即长度为4096的17个嵌入(每个令牌一个)。

下图是为了验证我们输入的这句话,是17个token。

手撕Llama3第1层: 从零开始实现llama3图片

代码如下:

embedding_layer = torch.nn.Embedding(vocab_size, dim)embedding_layer.weight.data.copy_(model["tok_embeddings.weight"])token_embeddings_unnormalized = embedding_layer(tokens).to(torch.bfloat16)token_embeddings_unnormalized.shape

手撕Llama3第1层: 从零开始实现llama3图片

三、构建Transformer的第一层

我们接着使用 RMS 归一化对嵌入进行归一化,也就是图中这个位置:

手撕Llama3第1层: 从零开始实现llama3图片

使用公式如下:

手撕Llama3第1层: 从零开始实现llama3图片

代码如下:

# def rms_norm(tensor, norm_weights):# rms = (tensor.pow(2).mean(-1, keepdim=True) + norm_eps)**0.5# return tensor * (norm_weights / rms)def rms_norm(tensor, norm_weights):return (tensor * torch.rsqrt(tensor.pow(2).mean(-1, keepdim=True) + norm_eps)) * norm_weights

这段代码定义了一个名为 rms_norm 的函数,它实现了对输入张量(tensor)的RMS(Root Mean Square,均方根)归一化处理。这个函数接受两个参数:tensor 和 norm_weights。tensor 是需要进行归一化处理的输入张量,而 norm_weights 是归一化时使用的权重。

函数的工作原理如下:

  1. 首先,计算输入张量每个元素的平方(tensor.pow(2))。
  2. 然后,对平方后的张量沿着最后一个维度(-1)计算均值(mean),并保持维度不变(keepdim=True),这样得到每个元素的均方值。
  3. 接着,将均方值加上一个很小的正数 norm_eps(为了避免除以零的情况),然后计算其平方根的倒数(torch.rsqrt),得到RMS的倒数。
  4. 最后,将输入张量与RMS的倒数相乘,再乘以归一化权重 norm_weights,得到归一化后的张量。

在进行归一化处理后,我们的数据形状仍然保持为 [17x4096],这与嵌入层的形状相同,只不过数据已经过归一化。

token_embeddings = rms_norm(token_embeddings_unnormalized, model["layers.0.attention_norm.weight"])token_embeddings.shape

手撕Llama3第1层: 从零开始实现llama3图片

手撕Llama3第1层: 从零开始实现llama3图片

接下来,我们介绍注意力机制的实现,也就是下图中的红框标注的位置:

手撕Llama3第1层: 从零开始实现llama3图片

手撕Llama3第1层: 从零开始实现llama3图片

我们一步一步地解释这张图,详细说明每个步骤。

1. 输入句子

  • 描述:这是我们的输入句子。
  • 解释:输入句子被表示为一个矩阵 ( X ),其中每一行代表一个词的嵌入向量。

2. 嵌入每个词

  • 描述:我们对每个词进行嵌入。
  • 解释:输入句子中的每个词被转换为一个高维向量,这些向量组成了矩阵 ( X )。

3. 分成8个头

  • 描述:将矩阵 ( X ) 分成8个头。我们用权重矩阵 ( W^Q )、( W^K ) 和 ( W^V ) 分别乘以 ( X )。
  • 解释:多头注意力机制将输入矩阵 ( X ) 分成多个头(这里是8个),每个头有自己的查询(Query)、键(Key)和值(Value)矩阵。具体来说,输入矩阵 ( X ) 分别与查询权重矩阵 ( W^Q )、键权重矩阵 ( W^K ) 和值权重矩阵 ( W^V ) 相乘,得到查询矩阵 ( Q )、键矩阵 ( K ) 和值矩阵 ( V )。

4. 计算注意力

  • 描述:使用得到的查询、键和值矩阵计算注意力。
  • 解释:对于每个头,使用查询矩阵 ( Q )、键矩阵 ( K ) 和值矩阵 ( V ) 计算注意力分数。具体步骤包括:

计算 ( Q ) 和 ( K ) 的点积。

对点积结果进行缩放。

应用softmax函数得到注意力权重。

用注意力权重乘以值矩阵 ( V ) 得到输出矩阵 ( Z )。

5. 拼接结果矩阵

  • 描述:将得到的 ( Z ) 矩阵拼接起来,然后用权重矩阵 ( W^O ) 乘以拼接后的矩阵,得到层的输出。
  • 解释:将所有头的输出矩阵 ( Z ) 拼接成一个矩阵,然后用输出权重矩阵 ( W^O ) 乘以这个拼接后的矩阵,得到最终的输出矩阵 ( Z )。

额外说明

  • 查询、键、值和输出向量的形状:在加载查询、键、值和输出向量时,注意到它们的形状分别是 [4096x4096]、[1024x4096]、[1024x4096]、[1024x4096] 和 [4096x4096]。
  • 并行化注意力头的乘法:将它们捆绑在一起有助于并行化注意力头的乘法。

这张图展示了Transformer模型中多头注意力机制的实现过程,从输入句子的嵌入开始,经过多头分割、注意力计算,最后拼接结果并生成输出。每个步骤都详细说明了如何从输入矩阵 ( X ) 生成最终的输出矩阵 ( Z )。

当我们从模型中加载查询(query)、键(key)、值(value)和输出(output)向量时,我们注意到它们的形状分别是 [4096x4096]、[1024x4096]、[1024x4096]、[4096x4096]

乍一看这很奇怪,因为理想情况下我们希望每个头的每个q、k、v和o都是单独的

print(model["layers.0.attention.wq.weight"].shape,model["layers.0.attention.wk.weight"].shape,model["layers.0.attention.wv.weight"].shape,model["layers.0.attention.wo.weight"].shape)

手撕Llama3第1层: 从零开始实现llama3Picture

쿼리 가중치 행렬(wq.weight)의 모양은 [4096, 4096]입니다. 키 가중치 행렬(wk.weight)의 모양은 [1024, 4096]입니다. 값 가중치 행렬(wv.weight)의 모양은 [1024, 4096]입니다. 출력(Output) 가중치 행렬(wo.weight)의 모양은 [4096, 4096]입니다. 출력 결과를 보면 쿼리(Q) 가중치 행렬과 출력(O) 가중치 행렬의 모양이 모두 동일한 것으로 나타났습니다[4096, 4096]. 이는 입력 기능과 출력 기능 모두 쿼리와 출력 모두에 대해 4096의 차원을 갖는다는 것을 의미합니다. 키(K) 가중치 행렬과 값(V) 가중치 행렬의 모양도 모두 동일합니다[1024, 4096]. 이는 키와 값의 입력 특성 차원이 4096이지만 출력 특성 차원은 1024로 압축되었음을 보여줍니다. 이러한 가중치 행렬의 모양은 모델 설계자가 주의 메커니즘의 다양한 부분의 크기를 설정하는 방법을 반영합니다. 특히 키와 값의 차원을 줄여 계산 복잡성과 메모리 소비를 줄이는 반면 쿼리와 출력의 차원을 높게 유지하면 더 많은 정보를 유지할 수 있습니다. 이 디자인 선택은 특정 모델 아키텍처 및 애플리케이션 시나리오에 따라 다릅니다.
이 그림의 주의 메커니즘을 설명하는 구현 프로세스를 단순화하기 위해 "나는 Li Hongzhang을 존경합니다"라는 문장을 예로 들어 보겠습니다. 문장을 입력하세요. 먼저 "나는 이홍장을 존경합니다"라는 문장이 있습니다. 이 문장을 처리하기 전에 문장의 각 단어를 수학적으로 처리 가능한 형식, 즉 단어 벡터로 변환해야 합니다. 이 프로세스를 워드 임베딩이라고 합니다.
워드 임베딩: "나", "감사", "이홍장" 등 각 단어가 고정된 크기의 벡터로 변환됩니다. 이러한 벡터에는 단어의 의미 정보가 포함되어 있습니다.
여러 머리로 분할: 모델이 다양한 관점에서 문장을 이해할 수 있도록 각 단어의 벡터를 여러 부분으로 나눕니다. 여기에 머리가 8개 있습니다. 각 머리는 문장의 다른 측면에 중점을 둡니다.
주의력 계산: 각 머리에 대해 주의력이라는 것을 계산합니다. 이 프로세스에는 세 단계가 포함됩니다. "리홍장에게 감사드립니다"를 예로 들어 "감사"라는 단어에 초점을 맞추려면 "감사"가 검색어이고 "나" 및 "리홍장"과 같은 다른 단어가 사용됩니다. 는 키입니다. 의 벡터는 값입니다.
질의(Q) : 정보를 찾고 싶은 부분입니다. 키(K): 정보를 담고 있는 부분입니다. 값(V): 실제 정보 내용입니다. 스플라이싱 및 출력: 각 머리의 주의력을 계산한 후 이 결과를 연결하고 가중치 행렬 Wo를 통해 최종 출력을 생성합니다. 이 출력은 다음 처리 계층이나 최종 결과의 일부로 사용됩니다.

그림에 대한 댓글에서 언급된 모양 문제는 이러한 벡터를 컴퓨터에서 효율적으로 저장하고 처리하는 방법에 관한 것입니다. 실제 코드 구현에서 효율성을 높이기 위해 개발자는 각 헤더를 개별적으로 처리하는 대신 여러 헤더의 쿼리, 키 및 값 벡터를 함께 패키징할 수 있습니다. 이는 최신 컴퓨터의 병렬 처리 기능을 활용하여 계산 속도를 높일 수 있습니다.

  • 쿼리 가중치 행렬(wq.weight)의 모양은 [4096, 4096]입니다.
  • 키 가중치 행렬(wk.weight)의 모양은 [1024, 4096]입니다.
  • 값 가중치 행렬(wv.weight)의 모양은 [1024, 4096]입니다.
  • 출력(Output) 가중치 행렬(wo.weight)의 모양은 [4096, 4096]입니다.

출력 결과는 다음과 같습니다.

  • 쿼리(Q) 및 출력(O) 가중치 행렬의 모양은 모두 [4096, 4096]으로 동일합니다. 이는 입력 기능과 출력 기능 모두 쿼리와 출력 모두에 대해 4096의 차원을 갖는다는 것을 의미합니다.
  • 키(K) 가중치 행렬과 값(V) 가중치 행렬의 모양도 [1024, 4096]으로 동일합니다. 이는 키와 값의 입력 특성 차원이 4096이지만 출력 특성 차원은 1024로 압축되었음을 보여줍니다.

이 가중치 행렬의 모양은 모델 디자이너가 주의 메커니즘의 다양한 부분의 크기를 설정하는 방법을 반영합니다. 특히 키와 값의 차원을 줄여 계산 복잡성과 메모리 소비를 줄이는 반면 쿼리와 출력의 차원을 높게 유지하면 더 많은 정보를 유지할 수 있습니다. 이 디자인 선택은 특정 모델 아키텍처 및 애플리케이션 시나리오에 따라 다릅니다.

이 그림에서 주의 메커니즘을 설명하는 구현 프로세스를 단순화하기 위해 "나는 Li Hongzhang을 존경합니다"라는 문장을 예로 사용하겠습니다.

  • 문장 입력: 먼저 "리홍장에게 감사드립니다"라는 문장이 있습니다. 이 문장을 처리하기 전에 문장의 각 단어를 수학적으로 처리 가능한 형식, 즉 단어 벡터로 변환해야 합니다. 이 프로세스를 워드 임베딩이라고 합니다.
  • 워드 임베딩: "나", "감사", "이홍장" 등 각 단어가 고정된 크기의 벡터로 변환됩니다. 이러한 벡터에는 단어의 의미 정보가 포함되어 있습니다.
  • 여러 머리로 분할: 모델이 다양한 관점에서 문장을 이해할 수 있도록 각 단어의 벡터를 여러 부분으로 나눕니다. 여기에 머리가 8개 있습니다. 각 머리는 문장의 다른 측면에 중점을 둡니다.
  • 주의력 계산: 각 머리에 대해 주의력이라는 것을 계산합니다. 이 프로세스에는 세 단계가 포함됩니다. "리홍장에게 감사드립니다"를 예로 들어 "감사"라는 단어에 초점을 맞추려면 "감사"가 검색어이고 "나" 및 "리홍장"과 같은 다른 단어가 사용됩니다. 는 키입니다. 의 벡터는 값입니다.

쿼리(Q) : 정보를 찾고 싶은 부분입니다.

키(K) : 정보를 담고 있는 부분입니다.

Value(V) : 실제 정보 내용입니다.

  • 접합 및 출력: 각 머리의 주의력을 계산한 후 이러한 결과를 함께 접합하고 가중치 행렬 Wo를 통해 최종 출력을 생성합니다. 이 출력은 다음 처리 계층이나 최종 결과의 일부로 사용됩니다.

그림에 대한 댓글에서 언급된 모양 문제는 이러한 벡터를 컴퓨터에서 효율적으로 저장하고 처리하는 방법에 관한 것입니다. 실제 코드 구현에서 효율성을 높이기 위해 개발자는 각 헤더를 개별적으로 처리하는 대신 여러 헤더의 쿼리, 키 및 값 벡터를 함께 패키징할 수 있습니다. 이는 최신 컴퓨터의 병렬 처리 기능을 활용하여 계산 속도를 높일 수 있습니다.

가중 행렬 WQ, WK, WV 및 WO의 역할을 설명하기 위해 "Li Hongzhang에게 감사드립니다"라는 문장을 계속 사용합니다.

Transformer 모델에서는 단어 임베딩을 통해 각 단어가 벡터로 변환됩니다. 그런 다음 이러한 벡터는 일련의 선형 변환을 거쳐 주의 점수를 계산합니다. 이러한 선형 변환은 가중치 행렬 WQ, WK, WV 및 WO를 통해 구현됩니다.

  1. WQ(가중치 행렬 Q): 이 행렬은 각 단어의 벡터를 "쿼리" 벡터로 변환하는 데 사용됩니다. 이 예에서는 "감사"라는 단어에 초점을 맞추려면 "감사" 벡터에 WQ를 곱하여 쿼리 벡터를 얻습니다.
  2. WK(가중치 행렬 K): 이 행렬은 각 단어의 벡터를 "키" 벡터로 변환하는 데 사용됩니다. 마찬가지로 "I"와 "Li Hongzhang"을 포함한 각 단어의 벡터에 WK를 곱하여 키 벡터를 얻습니다.
  3. WV(가중치 행렬 V): 이 행렬은 각 단어의 벡터를 "값" 벡터로 변환하는 데 사용됩니다. 각 단어 벡터에 WV를 곱하면 값 벡터를 얻습니다. 이 세 가지 행렬(WQ, WK, WV)은 각 헤더에 대해 서로 다른 쿼리, 키 및 값 벡터를 생성하는 데 사용됩니다. 이렇게 하면 각 머리가 문장의 다른 측면에 집중할 수 있습니다.
  4. WQ(가중치 행렬 Q), WK(가중치 행렬 K), WV(가중치 행렬 V) 및 WO(가중치 행렬 O)는 모델 훈련 중에 역전파 알고리즘을 통해 전달됩니다. 경사하강법과 같은 최적화 방법을 통해 학습됩니다.

전체 과정에서 WQ, WK, WV 및 WO는 모델이 입력 단어 벡터를 다른 표현으로 변환하는 방법과 이러한 표현을 결합하여 최종 출력을 얻는 방법을 결정합니다. 이러한 행렬은 Transformer 모델의 어텐션 메커니즘의 핵심 부분이며 모델이 문장에서 서로 다른 단어 간의 관계를 포착할 수 있도록 해줍니다.

WQ(가중치 행렬 Q), WK(가중치 행렬 K), WV(가중치 행렬 V) 및 WO(가중치 행렬 O)는 모델 학습 과정에서 역전파를 통해 전달됩니다. 알고리즘, 경사하강법 등의 최적화 방법을 통해 학습됩니다.

이 학습 과정이 어떻게 진행되는지 살펴보겠습니다.

  1. 初始化:在训练开始之前,这些矩阵通常会被随机初始化。这意味着它们的初始值是随机选取的,这样可以打破对称性并开始学习过程。
  2. 前向传播:在模型的训练过程中,输入数据(如句子“我欣赏李鸿章”)会通过模型的各个层进行前向传播。在注意力机制中,输入的词向量会与WQ、WK、WV矩阵相乘,以生成查询、键和值向量。
  3. 计算损失:模型的输出会与期望的输出(通常是训练数据中的标签)进行比较,计算出一个损失值。这个损失值衡量了模型的预测与实际情况的差距。
  4. 反向传播:损失值会通过反向传播算法传回模型,计算每个参数(包括WQ、WK、WV和WO)对损失的影响,即它们的梯度。
  5. 参数更新:根据计算出的梯度,使用梯度下降或其他优化算法来更新这些矩阵的值。这个过程会逐渐减小损失值,使模型的预测更加准确。
  6. 迭代过程:这个前向传播、损失计算、反向传播和参数更新的过程会在训练数据上多次迭代进行,直到模型的性能达到一定的标准或者不再显著提升。

    通过这个训练过程,WQ、WK、WV和WO这些矩阵会逐渐调整它们的值,以便模型能够更好地理解和处理输入数据。在训练完成后,这些矩阵将固定下来,用于模型的推理阶段,即对新的输入数据进行预测。

四、展开查询向量

在本小节中,我们将从多个注意力头中展开查询向量,得到的形状是 [32x128x4096] 这里,32 是 llama3 中注意力头的数量,128 是查询向量的大小,而 4096 是令牌嵌入的大小。

q_layer0 = model["layers.0.attention.wq.weight"]head_dim = q_layer0.shape[0] // n_headsq_layer0 = q_layer0.view(n_heads, head_dim, dim)q_layer0.shape

手撕Llama3第1层: 从零开始实现llama3图片

这段代码通过对模型中第一层的查询(Q)权重矩阵进行重塑(reshape),将其分解为多个注意力头的形式,从而揭示了32和128这两个维度。

  1. q_layer0 = model["layers.0.attention.wq.weight"]:这行代码从模型中提取第一层的查询(Q)权重矩阵。
  2. head_dim = q_layer0.shape[0] // n_heads:这行代码计算每个注意力头的维度大小。它通过将查询权重矩阵的第一个维度(原本是4096)除以注意力头的数量(n_heads),得到每个头的维度。如果n_heads是32(即模型设计为有32个注意力头),那么head_dim就是4096 // 32 = 128。
  3. q_layer0 = q_layer0.view(n_heads, head_dim, dim):这行代码使用.view()方法重塑查询权重矩阵,使其形状变为[n_heads, head_dim, dim]。这里dim很可能是原始特征维度4096,n_heads是32,head_dim是128,因此重塑后的形状是[32, 128, 4096]。
  4. q_layer0.shape 输出:torch.Size([32, 128, 4096]):这行代码打印重塑后的查询权重矩阵的形状,确认了其形状为[32, 128, 4096]。

之所以在这段代码中出现了32和128这两个维度,而在之前的代码段中没有,是因为这段代码通过重塑操作明确地将查询权重矩阵分解为多个注意力头,每个头具有自己的维度。32代表了模型中注意力头的数量,而128代表了分配给每个头的特征维度大小。这种分解是为了实现多头注意力机制,其中每个头可以独立地关注输入的不同部分,最终通过组合这些头的输出来提高模型的表达能力。 

实现第一层的第一个头

访问了第一层第一个头的查询(query)权重矩阵,这个查询权重矩阵的大小是 [128x4096]。

q_layer0_head0 = q_layer0[0]q_layer0_head0.shape

手撕Llama3第1层: 从零开始实现llama3图片

我们现在将查询权重与令牌嵌入相乘,以获得令牌的查询

在这里,你可以看到结果形状是 [17x128],这是因为我们有17个令牌,每个令牌都有一个长度为128的查询(每个令牌在一个头上方的查询)。

br

手撕Llama3第1层: 从零开始实现llama3Picture

이 코드는 토큰 임베딩(token_embeddings)을 첫 번째 레이어의 첫 번째 헤드 쿼리 가중치 행렬(q_layer0_head0)의 전치(.T)와 비교하여 행렬 곱셈 작업을 수행합니다. -토큰 쿼리 벡터(q_per_token).

  1. q_per_token = torch.matmul(token_embeddings, q_layer0_head0.T):

torch.matmul은 PyTorch의 행렬 곱셈 함수로 두 텐서의 곱셈을 처리할 수 있습니다.

token_embeddings는 각각 4096차원 임베딩 벡터로 표현되는 17개의 토큰을 나타내는 [17, 4096] 모양의 텐서여야 합니다.

q_layer0_head0은 첫 번째 레이어의 첫 번째 헤드에 대한 쿼리 가중치 행렬이며 원래 모양은 [128, 4096]입니다. .T는 q_layer0_head0의 모양을 [4096, 128]로 바꾸는 PyTorch의 전치 작업입니다.

이런 식으로 token_embeddings와 q_layer0_head0.T의 행렬 곱셈은 [17, 4096]과 [4096, 128]의 곱셈이고, 그 결과는 [17, 128] 모양의 텐서입니다.

  1. q_per_token.shape 및 출력: torch.Size([17, 128]):

이 코드 줄은 q_per_token 텐서의 모양을 인쇄하여 [17, 128]임을 확인합니다.

이는 입력된 모든 토큰(총 17개)에 대해 이제 128차원 쿼리 벡터를 갖게 된다는 의미입니다. 이 128차원 쿼리 벡터는 토큰 임베딩과 쿼리 가중치 행렬을 곱하여 얻어지며 후속 주의 메커니즘 계산에 사용될 수 있습니다.

요약하자면, 이 코드는 행렬 곱셈을 통해 각 토큰의 임베딩 벡터를 쿼리 벡터로 변환하여 어텐션 메커니즘 구현의 다음 단계를 준비합니다. 이제 각 토큰에는 이에 해당하는 쿼리 벡터가 있으며 이러한 쿼리 벡터는 다른 토큰과 함께 주의 점수를 계산하는 데 사용됩니다.

위 내용은 손으로 찢기 Llama3 레이어 1: 처음부터 llama3 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.