13. 嵌入权重
在自然语言处理(NLP)和机器学习中,嵌入权重(embedding weights)是用于将单词、短语或其他类型的标识符转换成密集的向量表示的参数。这些向量通常在较高维空间中(例如,几百维),并且可以捕捉到单词之间的复杂关系,如语义相似性。让我们深入了解一下嵌入权重的工作原理和它们的重要性。
13.1 嵌入权重的作用
- 语义表示:嵌入向量能够捕捉词汇的语义信息,使得语义上相近的词汇在向量空间中也相互接近。
- 维度压缩:将单词转换为密集向量可以大大减少模型需要处理的数据维度,相比于传统的 one-hot 编码方式,这样做可以提高模型的效率和性能。
- 关系学习:通过训练过程,模型可以学习到词汇之间的各种关系,包括但不限于同义词、反义词、上下位关系等。
13.2 嵌入层
在深度学习模型中,嵌入层(embedding layer)是一种特殊的层,负责学习和存储嵌入权重。嵌入层通常作为模型的第一层出现,直接处理输入的词汇标识符(如单词的索引)。
13.3 训练嵌入权重
嵌入权重可以通过多种方式获得:
- 预训练嵌入:使用大量文本数据预先训练嵌入向量,然后在特定任务的模型中使用这些预训练的向量。常见的预训练词嵌入包括Word2Vec、GloVe和FastText。
- 任务特定训练:在训练特定的NLP任务(如文本分类、情感分析等)时,从随机初始化的嵌入权重开始,让嵌入层作为模型的一部分进行端到端的训练。这样,嵌入权重会根据任务特定的数据进行优化。
13.4 使用场景
嵌入权重在各种NLP任务中都非常关键,包括:
- 文本分类
- 语言模型
- 机器翻译
- 实体识别
- 情感分析
通过有效地使用嵌入权重,深度学习模型能够更好地理解和处理自然语言,从而在各种NLP任务上取得更好的性能。
13.5 查看嵌入权重
根据前面保存的模型文件 output/model.bin 的内容,在了解 LLaMA-2 模型结构(2)章节里知道嵌入权重的矩阵如下:
1 |
tok_embeddings.weight: torch.Size([32000, 4096]) |
其中 32000 是词汇表大小(vocab_size), 4096 是模型的维度(dim), 矩阵里的数字类型是 float;
tok_embeddings.weight(下面演示的代码里为 token_embedding_table) 大小计算方法是
1 |
sizeof(tok_embeddings.weight) = vocab_size * dim * sizeof(float) |
可以知道,嵌入权重的起始位置是28,所占字节大小为 32000 * 4096 * 4 = 524,288,000 字节,可以换算为 500MB。
也可以得知,每一个词所占的空间,其实是每一行所站的空间,4096个浮点数,4096 * 4 = 16,384, 可以换算为 16KB。
命名为 test04.c,文件保存到 newsrc 目录下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <time.h> #include <math.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> // 用于存储和传递 Transformer 模型的超参数,即构建模型所需的各种参数。 // 在实际应用中,这个结构体的实例通常会作为参数传递给模型的构造函数,以初始化模型的结构。 typedef struct { // 代表 Transformer 模型的维度。在自然语言处理中,这个维度通常是嵌入(embedding)层和所有 Transformer 层的输出维度。 int dim; // transformer dimension // 代表前馈神经网络层(Feed Forward Neural Network Layer,即 Transformer 模型中的全连接层)的隐藏层维度。 int hidden_dim; // for ffn layers // 表示模型中的层数量。在一个标准的 Transformer 模型中,一个层包含一个多头自注意力机制(Multi-head Self Attention Mechanism)和一个前馈神经网络层。 int n_layers; // number of layers // 表示多头注意力机制中查询头的数量。多头注意力允许模型同时关注输入序列中的多个位置 int n_heads; // number of query heads // 表示键/值对的头的数量。这可以小于查询头的数量,因为有一些模型可能使用多查询功能。 int n_kv_heads; // number of key/value heads (can be < query heads because of multiquery) // 代表模型的词汇表大小。对于字节级的模型,词汇量通常为 256,因为有 256 个可能的字节值。 int vocab_size; // vocabulary size, usually 256 (byte-level) // 表示模型可以处理的最大序列长度。序列长度是输入到模型中的 token(如单词或字节)的数量。 int seq_len; // max sequence length } Config; int main(int argc, char *argv[]) { Config config; char *checkpoint = "../output/model.bin"; FILE *file = fopen(checkpoint, "rb"); if (!file) { fprintf(stderr, "Couldn't open file %s\n", checkpoint); exit(EXIT_FAILURE); } // 读取文件中的 Config 结构体信息。 if (fread(&config, sizeof(Config), 1, file) != 1) { fprintf(stderr, "Couldn't read file %s\n", checkpoint); exit(EXIT_FAILURE); } fclose(file); printf("sizeof(int) = %ld\n", sizeof(int)); printf("sizeof(float) = %ld\n", sizeof(float)); printf("sizeof(Config) = %ld\n", sizeof(Config)); printf("dim = %d\n", config.dim); printf("hidden_dim = %d\n", config.hidden_dim); printf("n_layers = %d\n", config.n_layers); printf("n_heads = %d\n", config.n_heads); printf("n_kv_heads = %d\n", config.n_kv_heads); printf("vocab_size = %d\n", config.vocab_size); printf("seq_len = %d\n", config.seq_len); int row_size = config.dim * sizeof(float); int weights_size = abs(config.vocab_size) * row_size; printf("row_size = %d(%d KB)\n", row_size, row_size / 1024); printf("weights_size = %d(%d MB)\n", weights_size, weights_size / (1024*1024)); return 0; } |
编译和运行 test04.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
make test04 cc test04.c -o test04 ./test04 sizeof(int) = 4 sizeof(float) = 4 sizeof(Config) = 28 dim = 4096 hidden_dim = 11008 n_layers = 32 n_heads = 32 n_kv_heads = 32 vocab_size = -32000 seq_len = 4096 row_size = 16384(16 KB) weights_size = 524288000(500 MB) |
14. 均方根归一化(RMSNorm)
在深度学习和自然语言处理(NLP)领域,RMSNorm
(Root Mean Square Normalization)是一种归一化技术,用于稳定深层网络中的训练。尽管 RMSNorm
并不是专门为注意力模型设计的,它可以有效地应用于包括 Transformer 在内的各种架构中,以优化训练过程和加速模型的收敛。
14.1 RMSNorm的工作原理
RMSNorm
通过对特定层的输出进行规范化处理,有助于防止深层网络中的梯度消失或梯度爆炸问题。具体来说,RMSNorm
会计算层输出的每个元素的平方根均值(Root Mean Square),并通过该值对输出进行缩放,从而使得规范化后的输出具有统一的方差。这样的处理可以帮助模型在训练过程中保持稳定的梯度范围。
14.2 RMSNorm 与 LayerNorm 的比较
RMSNorm
与另一种广泛使用的归一化技术 LayerNorm
(层归一化)相似,但二者在计算方式上存在差异。LayerNorm
会计算所有特征维度上的均值和标准差,并使用这些统计量来归一化特征。而 RMSNorm
仅仅基于均方根值进行规范化,不涉及均值的计算。这种差异使得 RMSNorm
在某些情况下可能更高效,因为它减少了计算量。
14.3 在注意力机制中应用 RMSNorm
在 Transformer 架构中,尤其是在自注意力(Self-Attention)和前馈网络(Feed-Forward Network)模块中,使用 RMSNorm
可以提高训练的稳定性和效率。将 RMSNorm
应用于这些模块的输出,有助于在不同的训练阶段维持激活值的规模,促进更快的收敛和更好的性能。
14.4 优势和局限
优势:
- 稳定性:通过维持激活值的规模,
RMSNorm
有助于稳定训练过程。 - 效率:由于计算简化,
RMSNorm
在某些情况下可能比LayerNorm
更加高效。
局限:
- 适用性:
RMSNorm
的效果可能会根据具体任务和模型架构而异。因此,它并不总是比LayerNorm
或其他归一化技术更优。 - 经验调参:尽管
RMSNorm
可以提高某些模型的性能,但在不同的应用中,可能需要通过实验来找到最佳的超参数设置。
总的来说,RMSNorm
是一个有用的工具,可以在特定情况下改善模型的训练过程和性能,但它的使用和优化可能需要根据具体的模型和任务进行调整。
问:RMSNorm 与 LayerNorm 是在训练中使用,还是在推理中使用,或是都需要使用,而且必须保持一致性?
RMSNorm(均方根归一化)与 LayerNorm(层归一化)都是在模型训练和推理中使用的,并且必须在这两个阶段保持一致性。这意味着,如果你在模型的训练过程中使用了这些归一化技术,那么在模型推理(或测试)阶段也应当使用相同的归一化方法。
这种一致性是因为归一化层成为了模型架构的一部分,对输入数据的处理方式直接影响模型的输出。在训练阶段使用归一化可以帮助模型学习更稳定和高效,通过调节数据分布来加快训练速度、提高模型性能,并帮助缓解梯度消失或爆炸的问题。在推理阶段,保持相同的数据处理流程确保了模型能够以与训练时相同的方式解释和处理输入数据,从而产生一致的、可靠的输出结果。
简而言之,RMSNorm 和 LayerNorm 既是训练过程的一部分,也是推理过程的一部分,二者必须保持一致,以确保模型行为的一致性和预测的准确性。
问:RMSNorm 权重大小是按层算的?
是的,RMSNorm(均方根归一化)的权重大小是按层计算的。具体来说,在RMSNorm中,对于给定层的输出,会计算该层所有输出元素的平方的平均值(即均方值),然后取其平方根(即均方根)。接着,使用这个均方根值来规范化该层的输出。这个过程是针对每个层独立进行的,因此可以说RMSNorm的权重大小是按层算的。
这种按层计算的方式有助于模型在不同层之间保持一致的规模和分布,从而有助于防止训练过程中的梯度消失或梯度爆炸问题。通过对每一层的输出进行归一化处理,RMSNorm可以改善训练稳定性,加速模型收敛,同时还能提高模型在多种任务上的性能。
attention_norm.weight(下面演示的代码里为 rms_att_weight) 大小计算方法是
1 |
sizeof(attention_norm.weight) = n_layers * dim * sizeof(float) |
代入数值进行计算:32 * 4096 * 4 = 524,288 = 512KB
每一次的权重大小为:4096 * 4 = 16384 = 16KB