[源码解析] PyTorch分布式优化器(2)----数据并行优化器

0x00 摘要

本系列介绍分布式优化器,分为三篇文章,分别是基石篇,DP/DDP/Horovod 之中数据并行的优化器,PyTorch 分布式优化器,按照深度递进。

本文介绍数据并行DP/DDP/Horovod 之中的优化器。

PyTorch分布式其他文章如下:

深度学习利器之自动微分(1)

深度学习利器之自动微分(2)

[源码解析]深度学习利器之自动微分(3) --- 示例解读

[源码解析]PyTorch如何实现前向传播(1) --- 基础类(上)

[源码解析]PyTorch如何实现前向传播(2) --- 基础类(下)

[源码解析] PyTorch如何实现前向传播(3) --- 具体实现

[源码解析] Pytorch 如何实现后向传播 (1)---- 调用引擎

[源码解析] Pytorch 如何实现后向传播 (2)---- 引擎静态结构

[源码解析] Pytorch 如何实现后向传播 (3)---- 引擎动态逻辑

[源码解析] PyTorch 如何实现后向传播 (4)---- 具体算法

[源码解析] PyTorch 分布式(1)------历史和概述

[源码解析] PyTorch 分布式(2) ----- DataParallel(上)

[源码解析] PyTorch 分布式(3) ----- DataParallel(下)

[源码解析] PyTorch 分布式(4)------分布式应用基础概念

[源码解析] PyTorch分布式(5) ------ DistributedDataParallel 总述&如何使用

[源码解析] PyTorch分布式(6) ---DistributedDataParallel -- 初始化&store

[源码解析] PyTorch 分布式(7) ----- DistributedDataParallel 之进程组

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

[源码解析] PyTorch 分布式(9) ----- DistributedDataParallel 之初始化

[源码解析] PyTorch 分布式(10)------DistributedDataParallel 之 Reducer静态架构

[源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer和Join操作

[源码解析] PyTorch 分布式(12) ----- DistributedDataParallel 之 前向传播

[源码解析] PyTorch 分布式(13) ----- DistributedDataParallel 之 反向传播

[源码解析] PyTorch 分布式 Autograd (1) ---- 设计

[源码解析] PyTorch 分布式 Autograd (2) ---- RPC基础

[源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关

[源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎

[源码解析] PyTorch 分布式 Autograd (5) ---- 引擎(上)

[源码解析] PyTorch 分布式 Autograd (6) ---- 引擎(下)

[源码解析] PyTorch分布式优化器(1)----基石篇

为了更好的说明,本文代码会依据具体情况来进行相应精简。

0x01 前文回顾

常规优化器主要功能就是使用梯度来进行优化,然后更新当前参数 : w.data -= w.grad * lr,而且是严格有条理的进行。

数据并行之中的优化器就是另外一种情况,因为每个worker自己计算梯度,所以优化器主要技术难点是:

  • 每个worker有自己的优化器?还是只有一个worker才有优化器,由他统一做优化?
  • 如果只有一个优化器,如何把各个worker的梯度合并起来,每个worker都传给这唯一的优化器?
  • 如果每个worker有自己优化器,本地优化器优化到本地模型之中,如何确保每个worker之中的模型始终保持一致?

这随着具体框架方案不同而有具体分别。

0x02 DP 之中的优化器

2.1 流程

DP 之中,我们需要注意的是,PyTorch 使用了多线程并行,所以应用之中只有一个优化器,这个优化器也是普通类型的优化器,其流程如下:

    1. 每个 GPU 在单独的线程上将针对各自的输入数据独立并行地进行 forward 计算,计算输出。
    1. 在 master GPU 之上收集(gather)输出。
    1. 在主GPU之上 计算损失。
    1. 把损失在 GPUs 之间 scatter。
    1. 在各个GPU之上运行后向传播,计算参数梯度。
    1. 在 GPU 0 之上归并梯度。
    1. 进行梯度下降,并用梯度更新主GPU上的模型参数
    1. 将更新后的模型参数复制到剩余的从属 GPU 中,进行后续迭代。

DP 修改了 forward 和 backward 方法,把每个线程的梯度归并在一起然后做优化,所以虽然是数据并行,但是优化器不需要做修改

2.2 使用

具体使用如下:

  1. model=torch.nn.DaraParallel(model);
  2. optimizer = torch.optim.SGD(model.parameters(), args.lr,
  3. momentum=args.momentum,
  4. weight_decay=args.weight_decay)
  5. for batch_idx, (data, label) in pbar:
  6. if args.cuda:
  7. data,label= data.cuda(),label.cuda(); # 数据放到了默认GPU
  8. data_v = Variable(data)
  9. target_var = Variable(label)
  10. prediction= model(data_v,target_var,args) # 多线程并行前向传播
  11. criterion = nn.CrossEntropyLoss()
  12. loss = criterion(prediction,target_var) # 在默认GPU之上计算loss
  13. optimizer.zero_grad()
  14. loss.backward() # 多线程并行后向传播
  15. optimizer.step() # 更新参数

我们给出一个简化的图示如下,每个thread进行梯度计算,最后把梯度归并到GPU 0,在GPU 0之上进行优化:

  1. Forward Backward
  2. +-------------------+ +------------------+
  3. +-->+ Thread 0 on GPU0 +--+ +-->+ Thread 1 on GPU0 +-+
  4. | +-------------------+ | GPU 0 | +------------------+ |
  5. | +-------------------+ | output +---------------+ loss | +------------------+ |
  6. +---->+ Thread 1 on GPU1 +---------> | Compute Loss +---------->+ Thread 2 on GPU1 +---+
  7. | | +-------------------+ | +---------------+ | +------------------+ | |
  8. | | +-------------------+ | | +------------------+ | |
  9. | +-->+ Thread 2 on GPU2 +--+ +-->+ Thread 3 on GPU2 +-+ |
  10. | +-------------------+ +------------------+ |
  11. | |
  12. | |
  13. | GPU 0 |
  14. | Model +-------------------------+ gradient |
  15. +--------------------------+ optimizer.step | <--------------------------------+
  16. +-------------------------+

0x03 DDP 之中的优化器

下图来自快手八卦的论文,图中罗列了原生训练过程与DDP/Horovod的对比。

  • 上面的 vanilla 就是原生训练过程,其中 U 部分对应的就是优化器过程。常规优化器主要功能就是根据梯度来更新模型当前参数 : w.data -= w.grad * lr
  • 下面部分就是DDP/Horovod优化过程,可以看到,其后向计算和归并梯度是部分并行的

3.1 流程

DDP 之中,依然使用的是普通优化器,但采用的是多进程方式,每个进程都完成训练的全部流程,只是在后向计算时候需要使用 all-reduce 来归并梯度。每个进程拥有自己独立的优化器,优化器也是常规优化器

这里有两个特点:

  • 每个进程维护自己的优化器,并在每次迭代中执行一个完整的优化步骤。虽然这可能看起来是多余的,但由于梯度已经聚合(gather)并跨进程平均,因此梯度对于每个进程都是相同的,这意味着不需要参数广播步骤,减少了在节点之间传输张量所花费的时间。
  • All-Reduce 操作是在后向传播之中完成的。
    • 在 DDP 初始化时候会生成一个Reducer,其内部会注册 autograd_hook。
    • autograd_hook 在反向传播时候进行梯度同步

DDP 选择了在 PyTorch 内核角度修改,在 DistributedDataParallel 模型的初始化和前向操作中做了处理。

具体逻辑如下:

  1. DDP 使用多进程并行加载数据,在 host 之上,每个worker进程都会把数据从硬盘加载到 page-locked memory。分布式 minibatch sampler 保证每个进程加载到的数据是彼此不重叠的。
  2. 不需要广播数据,而是并行把 minibatch 数据从 page-locked memory 加载到每个GPU,每个GPU都拥有模型的一个副本,所以也不需要拷贝模型。
  3. 在每个GPU之上运行前向传播,计算输出,每个GPU都执行同样的训练,不需要有主 GPU。
  4. 在每个GPU之上计算损失,运行后向传播来计算梯度,在计算梯度同时对梯度执行all-reduce操作
  5. 更新模型参数。因为每个GPU都从完全相同的模型开始训练,并且梯度被all-reduced,因此每个GPU在反向传播结束时最终得到平均梯度的相同副本,所有GPU上的权重更新都相同,这样所有 worker 上的模型都一致,也就不需要模型同步了

因为也是在模型的前向后向操作之中进行修改,所以优化器也不需要修改,每个worker分别在自己本地进程之中进行优化

3.2 优化器状态

这里要留意的是,如何保证各个进程的优化器状态相同?

DDP 与优化器实际上没有关联,DDP不对此负责,所以需要用户协同操作来保证各进程之间的优化器状态相同。这就围绕着两个环节:

  • 优化器参数初始值相同。

    • 优化器初始值相同由 "用户在DDP模型创建后才初始化optimizer" 来确保。
  • 优化器参数每次更新值相同。
    • 每次更新的梯度都是all-reduce过的,所以各个优化器拿到的梯度delta数值是一样的。

3.3 使用

其示例如下:

  1. model = ToyModel().to(rank)
  2. # 构造DDP model
  3. ddp_model = DDP(model, device_ids=[rank])
  4. loss_fn = nn.MSELoss()
  5. # 优化器要在构造DDP model之后,才能初始化。
  6. optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)
  7. optimizer.zero_grad()
  8. outputs = ddp_model(torch.randn(20, 10))
  9. labels = torch.randn(20, 5).to(rank)
  10. loss_fn(outputs, labels).backward()
  11. optimizer.step()

图示如下:

  1. +--------------------------------------------------------------------------------------+
  2. | Process 1 on GPU 1 |
  3. | +------------------------------+ |
  4. | | Backward | |
  5. | | | |
  6. | Forward +----> Loss +-----> | Compute +----> ALL+REDUCE | +----> Optimizer.step |
  7. | | ^ | |
  8. | | | | |
  9. | +------------------------------+ |
  10. | | |
  11. | | |
  12. +--------------------------------------------------------------------------------------+
  13. |
  14. |
  15. |
  16. |
  17. +
  18. SYNC GRADS
  19. +
  20. |
  21. |
  22. |
  23. +--------------------------------------------------------------------------------------+
  24. | Process 2 on GPU 2 | |
  25. | | |
  26. | +------------------------------+ |
  27. | | Backward | | |
  28. | | v | |
  29. | Forward +----> Loss +-----> | Compute +----> ALL+REDUCE | +----> Optimizer.step |
  30. | | | |
  31. | | | |
  32. | +------------------------------+ |
  33. | |
  34. +--------------------------------------------------------------------------------------+

0x04 Horovod 的优化器

Horovod 并没有对模型 fw/bw 进行修改(可能因为没有Facebook自己修改那么顺手),而是对优化器进行了修改,实现了一个 DistributedOptimizer。

我们以 horovod/torch/optimizer.py 为例。

  1. An optimizer that wraps another torch.optim.Optimizer, using an allreduce to
  2. combine gradient values before applying gradients to model weights.
  3. Allreduce operations are executed after each gradient is computed by ``loss.backward()``
  4. in parallel with each other. The ``step()`` method ensures that all allreduce operations are
  5. finished before applying gradients to the model.

DistributedOptimizer 包装了另一个torch.optim.optimizer,其作用是:

  • 在worker 并行执行loss.backward()计算出每个梯度之后,在 "将梯度应用于模型权重之前“ 这个时间点使用allreduce来合并梯度。
  • 使用step()方法来确保所有allreduce操作在将梯度应用于模型之前会完成。

其具体实现是 _DistributedOptimizer,而_DistributedOptimizer对于梯度的归并有两个途径,一个是通过 hook,一个是显性调用了 synchronize 函数,我们接下来逐一介绍。

4.1 hook 同步梯度

hook 就是采用了 PyTorch 的 hook 方法,和 DDP 的思路非常类似,即在梯度计算函数之上注册了hook,其作用是在计算完梯度之后调用hook,这样all-reduce 就是在计算梯度过程中自动完成的,不需要等待 step 方法显式调用来完成(类似 DP 那样),具体来说就是:

  1. 在每个GPU之上计算损失,运行后向传播来计算梯度,在计算梯度同时对梯度执行all-reduce操作
  2. 更新模型参数。因为每个GPU都从完全相同的模型开始训练,并且梯度被all-reduced,因此每个GPU在反向传播结束时最终得到平均梯度的相同副本,所有GPU上的权重更新都相同,也就不需要模型同步了。

注:代码主要分为两部分,处理 groups 相关 和 普通情况。

groups 是 PyTorch 的相关配置,作用是把梯度 allreduce 操作放在一起进行,因为代码比较复杂并且与本文主体逻辑不相关,所以我们略过这部分,只看普通非分组情况

  1. groups: The parameter to group the gradient allreduce ops. Accept values is a
  2. non-negative integer or a list of list of tf.Variable.
  3. If groups is a non-negative integer, it is the number of groups to assign
  4. gradient allreduce ops to for explicit grouping.
  5. If groups is a list of list of tf.Variable. Variables in the same
  6. inner list will be assigned to the same group, while parameter that does
  7. not appear in any list will form a group itself.
  8. Defaults as None, which is no explicit groups.

4.1.1 注册 hooks

Hook 功能分为两步骤,第一部分是注册 hooks。

  1. def _register_hooks(self):
  2. if self._groups is not None: # groups,有兴趣同学可以自行研究,可以理解为把梯度分组
  3. p_list = []
  4. # Get list of parameters with grads
  5. for param_group in self.param_groups:
  6. for p in param_group['params']:
  7. if p.requires_grad:
  8. p_list.append(p)
  9. # To ensure parameter order and group formation is consistent, broadcast p_list order
  10. # from rank 0 and use for every worker
  11. p_list_names = [self._parameter_names.get(p) for p in p_list]
  12. p_list_names = broadcast_object(p_list_names, root_rank=0)
  13. p_list = sorted(p_list, key=lambda p : p_list_names.index(self._parameter_names.get(p)))
  14. # Form groups
  15. if isinstance(self._groups, list):
  16. p_groups = []
  17. grouped_id = set()
  18. p_list_ids = [id(p) for p in p_list]
  19. for group in self._groups:
  20. p_groups.append([p for p in group if id(p) in p_list_ids])
  21. for p in p_groups[-1]:
  22. grouped_id.add(id(p))
  23. for p in p_list:
  24. if id(p) not in grouped_id:
  25. p_groups.append([p])
  26. else:
  27. p_groups = split_list(p_list, self._groups)
  28. p_groups = [tuple(p) for p in p_groups]
  29. for group in p_groups:
  30. for p in group:
  31. self._p_to_group[p] = group
  32. self._group_counts[group] = 0
  33. # 注册hooks
  34. for param_group in self.param_groups: # 遍历组
  35. for p in param_group['params']: # 遍历组中的参数
  36. if p.requires_grad: # 如果需要计算梯度
  37. p.grad = p.data.new(p.size()).zero_()
  38. self._requires_update.add(p)
  39. p_tmp = p.expand_as(p)
  40. grad_acc = p_tmp.grad_fn.next_functions[0][0] # 获取梯度函数
  41. grad_acc.register_hook(self._make_hook(p)) # 注册hook到梯度函数之上
  42. self._grad_accs.append(grad_acc)

_make_hook 会构建 hooks,返回了 hook 函数,该函数会在反向传播时候被调用,其内部执行了all-reduce。

  1. def _make_hook(self, p):
  2. def hook(*ignore):
  3. # 省略部分代码
  4. handle, ctx = None, None
  5. self._allreduce_delay[p] -= 1
  6. if self._allreduce_delay[p] == 0:
  7. if self._groups is not None: # 处理 groups 相关部分,我们略过
  8. group = self._p_to_group[p]
  9. self._group_counts[group] += 1
  10. if self._group_counts[group] == len(group):
  11. handle, ctxs = self._grouped_allreduce_grad_async(group) # 被调用时候会进行all-reduce
  12. self._handles[group] = (handle, ctxs)
  13. # Remove any None entries from previous no-op hook calls
  14. for gp in group:
  15. self._handles.pop(gp, None)
  16. self._group_counts[group] = 0
  17. return
  18. else:
  19. handle, ctx = self._allreduce_grad_async(p) # 被调用时候会进行all-reduce
  20. self._handles[p] = (handle, ctx) # 把handle注册到本地,后续会使用
  21. return hook

4.1.2 归并梯度

第二个阶段是归并,就是在反向传播阶段调用了 hook 函数,进行 all-reduce

  1. def _allreduce_grad_async(self, p):
  2. name = self._parameter_names.get(p)
  3. tensor = p.grad
  4. tensor_compressed, ctx = self._compression.compress(tensor)
  5. if self.op == Average:
  6. # Split average operation across pre/postscale factors
  7. # C++ backend will apply additional 1 / size() factor to postscale_factor for op == Average.
  8. prescale_factor = 1.0 / self.gradient_predivide_factor
  9. postscale_factor = self.gradient_predivide_factor
  10. else:
  11. prescale_factor = 1.0
  12. postscale_factor = 1.0
  13. # 调用 allreduce_async_ 完成 MPI 调用
  14. handle = allreduce_async_(tensor_compressed, name=name, op=self.op,
  15. prescale_factor=prescale_factor,
  16. postscale_factor=postscale_factor)
  17. return handle, ctx
  18. def _grouped_allreduce_grad_async(self, ps):
  19. name = self._parameter_names.get(ps[0])
  20. tensors_compressed, ctxs = zip(*[self._compression.compress(p.grad) for p in ps])
  21. handle = grouped_allreduce_async_(tensors_compressed, name=name, op=self.op)
  22. return handle, ctxs
4.1.2.1 MPI 函数

具体 MPI 函数位于 horovod/torch/mpi_ops.py

这里要点是:allreduce_async_ 返回了一个 handle,后续可以控制,比如 poll 或者 synchronize。

  1. def allreduce_async_(tensor, average=None, name=None, op=None,
  2. prescale_factor=1.0, postscale_factor=1.0):
  3. """
  4. A function that performs asynchronous in-place averaging or summation of the input
  5. tensor over all the Horovod processes.
  6. The reduction operation is keyed by the name. If name is not provided, an incremented
  7. auto-generated name is used. The tensor type and shape must be the same on all
  8. Horovod processes for a given name. The reduction will not start until all processes
  9. are ready to send and receive the tensor.
  10. Arguments:
  11. tensor: A tensor to reduce.
  12. average:
  13. .. warning:: .. deprecated:: 0.19.0
  14. Use `op` instead. Will be removed in v0.21.0.
  15. name: A name of the reduction operation.
  16. op: The reduction operation to combine tensors across different ranks. Defaults to
  17. Average if None is given.
  18. prescale_factor: Multiplicative factor to scale tensor before allreduce.
  19. postscale_factor: Multiplicative factor to scale tensor after allreduce.
  20. Returns:
  21. A handle to the allreduce operation that can be used with `poll()` or
  22. `synchronize()`.
  23. """
  24. op = handle_average_backwards_compatibility(op, average)
  25. return _allreduce_async(tensor, tensor, name, op, prescale_factor, postscale_factor)

_allreduce_async 位于 horovod/torch/mpi_ops.py,其从 MPI 库之中提取函数进行处理。

  1. def _allreduce_async(tensor, output, name, op, prescale_factor, postscale_factor):
  2. # Set the divisor for reduced gradients to average when necessary
  3. if op == Average:
  4. if rocm_built():
  5. # For ROCm, perform averaging at framework level
  6. divisor = size()
  7. op = Sum
  8. else:
  9. divisor = 1
  10. elif op == Adasum:
  11. if tensor.device.type != 'cpu' and gpu_available('torch'):
  12. if nccl_built():
  13. if rocm_built():
  14. # For ROCm, perform averaging at framework level
  15. divisor = local_size()
  16. else:
  17. divisor = 1
  18. else:
  19. divisor = 1
  20. else:
  21. divisor = 1
  22. else:
  23. divisor = 1
  24. function = _check_function(_allreduce_function_factory, tensor)
  25. try:
  26. handle = getattr(mpi_lib, function)(tensor, output, divisor,
  27. name.encode() if name is not None else _NULL, op,
  28. prescale_factor, postscale_factor)
  29. except RuntimeError as e:
  30. raise HorovodInternalError(e)
  31. _handle_map[handle] = (tensor, output)
  32. return handle
4.1.2.2 原理图

这个图和DDP类似,因此略过。

4.2 step 同步梯度

step 是另外一个进行all-reduce 操作的途径。

step函数定义如下,可以看到,如果需要强制同步,就调用self.synchronize(),否则就调用基类的 step 函数来更新参数。

  1. def step(self, closure=None):
  2. if self._should_synchronize:
  3. if self._synchronized:
  4. warnings.warn("optimizer.step() called without "
  5. "optimizer.skip_synchronize() context after "
  6. "optimizer.synchronize(). This can cause training "
  7. "slowdown. You may want to consider using "
  8. "optimizer.skip_synchronize() context if you use "
  9. "optimizer.synchronize() in your code.")
  10. self.synchronize()
  11. self._synchronized = False
  12. return super(self.__class__, self).step(closure)

4.2.1 synchronize

上面提到了 synchronize,我们下面就仔细研究一下。

从注释中可以了解,synchronize() 是用来强制allreduce 操作完成,这对于梯度裁剪(gradient

clipping)或者其他有 in place 梯度修改的操作特别有用,这些操作需要在step()之前完成。

synchronize() 需要和 optimizer.skip_synchronize()一起合作。

  1. DistributedOptimizer exposes the ``synchronize()`` method, which forces allreduce operations
  2. to finish before continuing the execution. It's useful in conjunction with gradient
  3. clipping, or other operations that modify gradients in place before ``step()`` is executed.
  4. Make sure to use ``optimizer.skip_synchronize()`` if you're calling ``synchronize()``
  5. in your code.

4.2.2 梯度裁剪

首先要了解什么是梯度爆炸,梯度爆炸指的是在模型训练过程之中,因为梯度变得太大而使得模型不稳定,容易直接跳过最优解。梯度裁剪(gradient clipping)就是一种解决梯度爆炸的技术 :如果梯度变得太大,那么就调节它使其保持较小的状态,这样可以避免模型越过最优点。

为了和梯度裁剪协同,需要在 step 之前调用 synchronize 以强制 all-reduce 完成。源码中的例子如下:

  1. output = model(data)
  2. loss = F.nll_loss(output, target)
  3. loss.backward()
  4. optimizer.synchronize()
  5. torch.nn.utils.clip_grad_norm_(model.parameters(), args.clip)
  6. with optimizer.skip_synchronize():
  7. optimizer.step()

4.2.3 实现

我们接下来看看 synchronize 的实现。这里最重要的是 outputs = synchronize(handle) 调用了 horovod.torch.mpi_ops.synchronize 完成了同步操作,这地方很容易让新手误解,因为名字相同,容易误会成递归。

  1. from horovod.torch.mpi_ops import synchronize
  2. def synchronize(self):
  3. completed = set()
  4. for x in self._handles.keys():
  5. completed.update(x) if isinstance(x, tuple) else completed.add(x)
  6. missing_p = self._requires_update - completed # 找到目前没有计算完毕的梯度
  7. for p in missing_p:
  8. handle, ctx = self._allreduce_grad_async(p) # 对于没有计算完毕的,显式进行all-reduce
  9. self._handles[p] = (handle, ctx) # 记录下来本次计算的handle
  10. for p, (handle, ctx) in self._handles.items():
  11. if handle is None: # 如果没有记录调用过all-reduce
  12. handle, ctx = self._allreduce_grad_async(p) # 进行all-reduce
  13. self._handles[p] = (handle, ctx)
  14. for p, (handle, ctx) in self._handles.items(): # 最后统一进行同步!
  15. if isinstance(p, tuple):
  16. # This was a grouped result, need to unpack
  17. outputs = synchronize(handle) # 调用 mpi 同步操作
  18. for gp, output, gctx in zip(p, outputs, ctx):
  19. self._allreduce_delay[gp] = self.backward_passes_per_step
  20. gp.grad.set_(self._compression.decompress(output, gctx))
  21. else:
  22. output = synchronize(handle) # 调用 mpi 同步操作
  23. self._allreduce_delay[p] = self.backward_passes_per_step
  24. p.grad.set_(self._compression.decompress(output, ctx))
  25. self._handles.clear()
  26. self._synchronized = True

4.2.4 MPI 同步操作

代码位于 horovod/torch/mpi_ops.py,直接调用了MPI 库函数,有兴趣同学可以自己深入研究。

  1. def synchronize(handle):
  2. """
  3. Synchronizes an asynchronous allreduce, allgather or broadcast operation until
  4. it's completed. Returns the result of the operation.
  5. Arguments:
  6. handle: A handle returned by an allreduce, allgather or broadcast asynchronous
  7. operation.
  8. Returns:
  9. An output tensor of the operation.
  10. """
  11. if handle not in _handle_map:
  12. return
  13. try:
  14. mpi_lib.horovod_torch_wait_and_clear(handle)
  15. output = _handle_map.pop(handle)[-1]
  16. return output
  17. except RuntimeError as e:
  18. raise HorovodInternalError(e)

4.2.5 图示

目前逻辑如下图所示:

  1. +---------------------------------------------------------------------------------+
  2. | Process 1 on GPU 1 |
  3. | +----------------------------+ |
  4. | | Optimizer | |
  5. | | | |
  6. | Forward +----> Loss +-----> Backward +----> | ALL-REDUCE +----> step | |
  7. | | | |
  8. | | ^ | |
  9. | | | | |
  10. | +----------------------------+ |
  11. | | |
  12. +---------------------------------------------------------------------------------+
  13. |
  14. |
  15. |
  16. |
  17. |
  18. SYNC|GRADS
  19. |
  20. |
  21. |
  22. |
  23. +----------------------------------------------------------------------------------+
  24. | Process 2 on GPU 2 | |
  25. | | |
  26. | +-----------------------------+ |
  27. | | Optimizer | | |
  28. | | | | |
  29. | Forward +----> Loss +-----> Backward +----> | v | |
  30. | | ALL-REDUCE +----> step | |
  31. | | | |
  32. | +-----------------------------+ |
  33. | |
  34. +----------------------------------------------------------------------------------+

至此,数据并行优化器分析完毕,下一篇我们介绍PyTorch 分布式优化器,敬请期待。

0xFF 参考

torch.optim.optimizer源码阅读和灵活使用

pytorch源码阅读(二)optimizer原理

pytorch 优化器(optim)不同参数组,不同学习率设置的操作

Pytorch——momentum动量

各种优化方法总结比较(sgd/momentum/Nesterov/adagrad/adadelta)

【优化器】优化器算法及PyTorch实现(一):永不磨灭的SGD

以optim.SGD为例介绍pytorch优化器

Pytorch学习笔记08----优化器算法Optimizer详解(SGD、Adam)

pytorch中使用torch.optim优化神经网络以及优化器的选择 - pytorch中文网

pytorch优化器详解:SGD

Pytorch里addmm()和addmm_()的用法详解

PyTorch下的可视化工具

PyTorch的优化器

[源码解析] PyTorch分布式优化器(2)----数据并行优化器的更多相关文章

  1. [源码解析] PyTorch分布式优化器(1)----基石篇

    [源码解析] PyTorch分布式优化器(1)----基石篇 目录 [源码解析] PyTorch分布式优化器(1)----基石篇 0x00 摘要 0x01 从问题出发 1.1 示例 1.2 问题点 0 ...

  2. [源码解析] PyTorch分布式优化器(3)---- 模型并行

    [源码解析] PyTorch分布式优化器(3)---- 模型并行 目录 [源码解析] PyTorch分布式优化器(3)---- 模型并行 0x00 摘要 0x01 前文回顾 0x02 单机模型 2.1 ...

  3. [源码解析] PyTorch 分布式(1)------历史和概述

    [源码解析] PyTorch 分布式(1)------历史和概述 目录 [源码解析] PyTorch 分布式(1)------历史和概述 0x00 摘要 0x01 PyTorch分布式的历史 1.1 ...

  4. [源码解析] PyTorch 分布式(2) ----- DataParallel(上)

    [源码解析] PyTorch 分布式(2) ----- DataParallel(上) 目录 [源码解析] PyTorch 分布式(2) ----- DataParallel(上) 0x00 摘要 0 ...

  5. [源码解析] PyTorch分布式(5) ------ DistributedDataParallel 总述&如何使用

    [源码解析] PyTorch 分布式(5) ------ DistributedDataParallel 总述&如何使用 目录 [源码解析] PyTorch 分布式(5) ------ Dis ...

  6. [源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

    [源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇 目录 [源码解析] PyTorch 分布式(8) -------- Distrib ...

  7. [源码解析] PyTorch 分布式(9) ----- DistributedDataParallel 之初始化

    [源码解析] PyTorch 分布式(9) ----- DistributedDataParallel 之初始化 目录 [源码解析] PyTorch 分布式(9) ----- DistributedD ...

  8. [源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer

    [源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer 目录 [源码解析] PyTorch 分布式(11) ----- Dis ...

  9. [源码解析] PyTorch 分布式 Autograd (1) ---- 设计

    [源码解析] PyTorch 分布式 Autograd (1) ---- 设计 目录 [源码解析] PyTorch 分布式 Autograd (1) ---- 设计 0x00 摘要 0x01 分布式R ...

随机推荐

  1. Luogu 520题纪念

    一入OI深似海......

  2. oeasy教您玩转vim - 57 - # 行可视化

    ​ 可视化编辑 回忆上节课内容 上次我们了解到可视模式 其实可视化对应三种子模式 字符可视模式 v 行可视模式 大写V 块可视模式ctrl+v 我们先来了解字符可视化模式 快捷键 v 可配合各种mot ...

  3. no active checks on server [192.168.1.101:10051]: host [ ] not found

    1.查看/etc/zabbix/zabbix_agentd.conf的 hostanme 2.查看zabbix-web中,主机名称 3.将zabbix-web的主机名改成/etc/zabbix/zab ...

  4. 组件通过props属性传值

    组件之间的传值 组件是一个单独功能模块的封装,有属于自己的data和methods,一个组件的 data 选项必须是一个函数 为什么必须是函数:因为只有当data是函数时,不同实例调用同一个组件时才会 ...

  5. 【SVG】为了前端页面的美丽,我选择学习SVG

    [SVG]为了前端页面的美丽,我选择学习SVG 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 SVG在之前自学的过程中, ...

  6. Linux ns 5. IPC Namespace 详解

    文章目录 1. 简介 2. 源码分析 2.1 copy_ipcs() 2.2 ipcget() 2.3 ipc_check_perms() 2.4 相关系统调用 参考文档: 1. 简介 进程间通讯的机 ...

  7. airflow redis sentinel

    获取master name subscribe __sentinel__:hello mysql plugin table not found mysqld --initialize-insecure ...

  8. 菜鸡的Java笔记 简单JAVA 类的开发原则以及具体实现

    /*  现在要求定义一个雇员信息类 在这个类之中包含有雇员编号 姓名 职位 基本工资 佣金等信息    对于此时给定要求实际上就是描述一类事物,而这样的程序类在在java之中可以将其称为简单java类 ...

  9. PHP 数组函数分类整理

    1.处理数组键名相关的函数: array_change_key_case - 返回字符串键名全为小写或大写的数组. array_key_exists - 检查给定的键名或索引是否存在于数组中 arra ...

  10. [Aizu1410]Draw in Straight Lines

    注意到当操作确定后,显然操作顺序总是涂黑色的1操作->涂白色的1操作->2操作 用$b/w_{r/c}(i,j)$表示$(i,j)$是否被黑色/白色 横着/竖着 涂过(1表示涂过,0表示没 ...