入门小菜鸟,希望像做笔记记录自己学的东西,也希望能帮助到同样入门的人,更希望大佬们帮忙纠错啦~侵权立删。
目录
D、分别获取分布式训练中给每个进程开始词和结束词的index列表
B、构建输入词在该进程中的index列表,同时对是否全在该范围内进行标识
一、用途
转化为词向量咯
二、代码解析
这一部分在mpu/layers.py里的VocabParallelEmbedding类里实现
1、__init__
def __init__(self, num_embeddings, embedding_dim,
init_method=init.xavier_normal_):
super(VocabParallelEmbedding, self).__init__()
A、参数意义
num_embeddings查询表(词典)的大小(即有多少个词);
embedding_dim每个查询向量的维度(即为每个词创建一个多少维的向量来表示);
init_method初始化权重方法————这里初始化为服从正态分布
B、对查询表的设定
# Keep the input dimensions.
self.num_embeddings = num_embeddings
self.embedding_dim = embedding_dim
C、对模型的一些其他设定(包括对权重,是否填充等等)
# Set the detauls for compatibility.
self.padding_idx = None
#padding_idx选择了不填充
self.max_norm = None
#max_norm权重约束————这里选择了最大范数不做约束
self.norm_type = 2.
#norm_type指定利用2范数计算。
self.scale_grad_by_freq = False
#scale_grad_by_freq根据单词在mini-batch(一个batchsize)中出现的频率,对梯度进行放缩.这里不启用。
self.sparse = False
#这里不启用专门处理稀疏张量的模块
self._weight = None
(1)padding_idx——自然语言中使用批处理时候, 每个句子的长度并不一定是等长的, 这时候就需要对较短的句子进行padding。这里默认选择了不填充
(2)max_norm权重约束——最大范数:如果嵌入向量的范数超过了这个界限,就要进行再归一化。这里默认不做约束
(3)norm_type指定利用什么范数计算,并用于对比max_norm,这里选择2范数。
(4)scale_grad_by_freq根据单词在mini-batch(一个batchsize)中出现的频率,对梯度进行放缩
(5)sparse:torch.sparse是一个专门处理稀疏张量的模块。(通常,张量会按一定的顺序连续地进行存取。但是,对于一个存在很多空值的稀疏张量来说,顺序存储的效率显得较为低下)只有足够稀疏的张量使用这种方式进行存储才能获得更高的效率。——这里不启用。
补充说明:sparse中,有意义的值被称为specified elements,而无意义的值(空值,通常为0,但是也可以是其他值)则被称为fill value。
(6)_weight:这个好像没什么用啊(如果他有什么用处的话可不可以在评论区里吱我一声呀,谢谢啦~)
D、分别获取分布式训练中给每个进程开始词和结束词的index列表
#分别获取分布式训练中给每个进程开始词和结束词的index列表
self.vocab_start_index, self.vocab_end_index = \
VocabUtility.vocab_range_from_global_vocab_size(
self.num_embeddings, get_model_parallel_rank(),
get_model_parallel_world_size())
这里调用了mpu/utils.py里的VocabUtility类中的vocab_range_from_global_vocab_size方法,mpu/initialize.py里的get_model_parallel_rank函数和get_model_parallel_world_size函数。
(1)VocabUtility类中的vocab_range_from_global_vocab_size方法
@staticmethod #参数设定:global_vocab_size——词典中词的数量;rank——模型并行组的进程标识(模型并行组中每个进程都有唯一标识符);world_size——分布式组中的进程数 def vocab_range_from_global_vocab_size(global_vocab_size, rank, world_size): per_partition_vocab_size = divide(global_vocab_size, world_size)#得到一个进程里有多少词 return VocabUtility.vocab_range_from_per_partition_vocab_size( per_partition_vocab_size, rank, world_size)
✨参数设定:
- global_vocab_size——词典中词的数量
- rank——模型并行组的进程标识(模型并行组中每个进程都有唯一标识符)
- world_size——分布式组中的进程数
✨divide函数(mpu/utils.py里)——就是实现个保证可以整除,这里为了得到一个进程中的词数量
def ensure_divisibility(numerator, denominator): """Ensure that numerator is divisible by the denominator.""" assert numerator % denominator == 0, '{} is not divisible by {}'.format( numerator, denominator) def divide(numerator, denominator): """Ensure that numerator is divisible by the denominator and return the division value.""" ensure_divisibility(numerator, denominator)#调用上面的函数:判断是否整除 return numerator // denominator #直接进行一个强制整除返回
✨VocabUtility类中的vocab_range_from_per_partition_vocab_size方法——获取进程开始词和结束词的index(这里的world_size应该是没有用的)
#获取进程开始词和结束词的index #参数说明:per_partition_vocab_size——一个进程中词的数目;rank——每个进程的标识 @staticmethod def vocab_range_from_per_partition_vocab_size(per_partition_vocab_size, rank, world_size): index_f = rank * per_partition_vocab_size#计算每个进程开始词的表示index index_l = index_f + per_partition_vocab_size#计算每个进程组结束词的表示index return index_f, index_l
(2)get_model_parallel_rank()——返回模型并行组的进程标识
#返回模型并行组的进程标识
def get_model_parallel_rank():
"""Return my rank for the model parallel group."""
return torch.distributed.get_rank(group=get_model_parallel_group())
(3)get_model_parallel_world_size()——返回分布式组中的进程数
#返回分布式组中的进程数
def get_model_parallel_world_size():
"""Return world size for the model parallel group."""
return torch.distributed.get_world_size(group=get_model_parallel_group())
(4)get_model_parallel_group()——判断是否有模型并行组,有则返回该模型并行组。
(get_model_parallel_rank()和get_model_parallel_world_size()都用到了这个函数
def get_model_parallel_group():
"""Get the model parallel group the caller rank belongs to."""
assert _MODEL_PARALLEL_GROUP is not None, \
'model parallel group is not initialized'
return _MODEL_PARALLEL_GROUP
E、获得相应的初始化权重参数矩阵
(1)获取每个进程词的数目(即输入的矩阵的行数)的列表
#获取每个进程词的数目(即输入的矩阵的行数)
self.num_embeddings_per_partition = self.vocab_end_index - \
self.vocab_start_index
(2)调用torch.nn.parameter的Parameter类获得权重矩阵的格式
#获得权重矩阵的格式
self.weight = Parameter(torch.Tensor(self.num_embeddings_per_partition,
self.embedding_dim))
(3)启用模型并行
self.weight.model_parallel = True
(4)初始化权重矩阵
#初始化权重矩阵
_initialize_affine_weight(
self.weight, self.num_embeddings, self.embedding_dim,
self.num_embeddings_per_partition, 0, init_method)
这里调用了_initialize_affine_weight函数
#参数设定:weight——权重矩阵格式;output_size权重矩阵的行数;input_size权重矩阵的列数;per_partition_size记录每个进程的大小的列表;partition_dim进程深度(分割张量的维度);init_method初始化权重矩阵的方法;stride步幅;return_master_weight是否返回矩阵 def _initialize_affine_weight(weight, output_size, input_size, per_partition_size, partition_dim, init_method, stride=1, return_master_weight=False): """Initialize affine weight for model parallel. Build the master weight on all processes and scatter the relevant chunk."""
✨参数设定:
- weight——权重矩阵格式;
- output_size权重矩阵的行数;
- input_size权重矩阵的列数;
- per_partition_size记录每个进程的大小的列表;
- partition_dim进程深度(分割张量的维度);
- init_method初始化权重矩阵的方法;
- stride步幅;
- return_master_weight是否返回矩阵
✨获取进程数(每个进程组里有多少个进程)——默认情况下,只有一个进程组
world_size = get_model_parallel_world_size()#获取进程数(每个进程组里有多少个进程)
✨若只有一个进程,直接按初始化方法初始化权重后结束
if world_size == 1: init_method(weight) if return_master_weight: return weight return None
✨初始化一个output_size*input_size的矩阵
master_weight = torch.empty(output_size, input_size, dtype=weight.dtype, requires_grad=False) init_method(master_weight)#用特定的初始化方法初始化该矩阵
✨按进程拆分矩阵
per_partition_per_stride_size = divide(per_partition_size, stride)#得到一个进程每一份的大小(行数) weight_list = torch.split(master_weight, per_partition_per_stride_size, dim=partition_dim)#将权重矩阵分为per_partition_per_stride_size大小的几份——这里相当于按进程划分 rank = get_model_parallel_rank()#获取模型并行组的进程标识 my_weight_list = weight_list[rank::world_size]#以rank为起始以进程数为步长的权重列表
✨拼接
with torch.no_grad(): torch.cat(my_weight_list, dim=partition_dim, out=weight)#拼接,输出张量为weight if return_master_weight: return master_weight return None
2、forward
A、判断输入的词的index是否在进程index中
def forward(self, input_):
# Build the mask.
input_mask = (input_ < self.vocab_start_index) | \
(input_ >= self.vocab_end_index)#输入的index是否全在该进程index中(在为false)
B、构建输入词在该进程中的index列表,同时对是否全在该范围内进行标识
masked_input = input_.clone() - self.vocab_start_index#得出input是在该进程中的第几个词
masked_input[input_mask] = 0#标识输入的index是否全在该进程index中(第0元素为0说明全都在)
C、获取嵌入词
output_parallel = F.embedding(masked_input, self.weight,
self.padding_idx, self.max_norm,
self.norm_type, self.scale_grad_by_freq,
self.sparse)
D、标识记录并进行数据汇总
# Mask the output embedding.
output_parallel[input_mask, :] = 0.0#将标识的那一行全部变为0
# Reduce across all the model parallel GPUs.
output = reduce_from_model_parallel_region(output_parallel)#对所有进程内的数据进行汇总,并且让所有进程都获取最终结果
return output
这里的reduce_from_model_parallel_region()调用了torch.distributed.all_reduce()函数。
原理如下:
欢迎大家在评论区中批评指正,谢谢~