ShardingStage1_Zero1两种实现
DygraphShardingOptimizer简单介绍

ZeRo是由DeepSpeed提出的优化方法,一开始主要是为了缓解DP场景下的显存压力。分为:
ZeRo-1: 优化器状态分片
ZeRo-2: 优化器状态与梯度分片
ZeRo-3: 优化器状态、梯度和模型权重参数分片
当前paddle中实现的DygraphShardingOptimizer和DygraphShardingOptimizerV2的思想和Zero-1的思想是相对应的,即仅仅对优化器状态进行分片,分摊到不同卡上。
1.DygraphShardingOptimizer
主要划分param的方法如下图所示(划分前可以选择是否对param按大小排序):

这里注意,首先,param的划分,是参数间的划分,不会对一个param内部去做切分;其次param在内存中可能是非连续存储的,并且这里分配param,仅仅做param到rank的映射,并不改变param在内存中存储的位置。对于DygraphShardingOptimizer的参数划分比较简单,其重点在于两个操作,tensor_fusion和communication_overlap,因此在后续主要介绍这两点。
1.1 tensor_fusion

为了提高整个计算效率,DygraphShardingOptimizer可以选择是否做参数聚合,整个调用链路如下:
_tensor_fusion -> fused_parameters -> _fused_parameters_impl -> obtain_storage -> 创建FusedCommBuffer
而聚合的param列表,在聚合前,会经过不同类型做层层筛选,筛选流程如下:

先根据sharding_rank划分,接着根据dtype、dist、need_clip等属性划分,再根据是否是decay_param做划分,最后会根据设定的group_size,使用贪婪算法,即每放满一个group,就另外新建一个group放置param。而聚合param,主要用到的类是FusedCommBuffer,注意当前rank上的所有group包含的param,只有当前rank上分到的param,因此FusedCommBuffer处理的也仅仅是当前rank对应的param,而这一点会和DygraphShardingOptimizer有所区别。

FusedCommBuffer这个类的主要作用其实就是为了将同一个group的所有param给放到一个连续的内存上,首先它会将param的维度展开摊平成一维,接着乘以dtype对应的字节大小,得到整个param对应的字节大小,紧接着进行内存对齐,目前gpu,xpu,npu都是256(这里做内存对齐的主要目的是提高内存访问效率)

如上图图1所示,如果不做内存对齐,可能访问一 个param要连续访问两个内存块,而如果做了内存对齐,此时只用访问一个内存块,提高了访存效率。
这里做了param对齐后,接着同样会对grad也按照上述方法做对齐,将grad也放置到一个连续的内存空间,buffer大小和param_buffer的大小是一样的。(注意这里提到的参数与参数梯度,都是optimizer_param_list中的参数,而实际每个rank上的model的param仍然都是全量的,只是优化器视角,以及后续创建的优化器状态是会划分了的)。
1.2 comm_overlap



这里如果设置了通信重叠,则对于每个param,都会注册一个backward的hook,在反向过程中,每当当前这个param的grad计算完成后,就会立即触发hook,做add_grad操作,而add_grad操作主要作用是对计算好的grad做grad通信,其中如果设置了release_grads,则每次调用buffer._comm_grads后,都会清除掉当前的grad_buffer,因此,在add_grad中如果判断设置了release_grads,则会调用_copy_grad_to_buffer函数,再次初始化grad_buffer,并将新一轮的grad映射到grad_buffer上。