[源码解析] 深度学习分布式训练框架 horovod (17) --- 弹性训练之容错

0x00 摘要

Horovod 是Uber于2017年发布的一个易于使用的高性能的分布式训练框架,在业界得到了广泛应用。

本系列将通过源码分析来带领大家了解 Horovod。本文是系列第十七篇,看看horovod 的容错机制。

我们依然用问题来引导学习。

问题是:

  • 这些异常是 每个 worker 自动发出的吗?
  • 是 worker 们一起抛出异常吗?
  • 这些异常怎么通知给 Driver?

我们下面一一分析(为了可以独立成文,本文部分原理内容与前文相同)。

本系列其他文章链接如下:

[源码解析] 深度学习分布式训练框架 Horovod (1) --- 基础知识

[源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入

[源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun背后做了什么

[源码解析] 深度学习分布式训练框架 horovod (4) --- 网络基础 & Driver

[源码解析] 深度学习分布式训练框架 horovod (5) --- 融合框架

[源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构

[源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer

[源码解析] 深度学习分布式训练框架 horovod (8) --- on spark

[源码解析] 深度学习分布式训练框架 horovod (9) --- 启动 on spark

[源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark

[源码解析] 深度学习分布式训练框架 horovod (11) --- on spark --- GLOO 方案

[源码解析] 深度学习分布式训练框架 horovod (12) --- 弹性训练总体架构

[源码解析] 深度学习分布式训练框架 horovod (13) --- 弹性训练之 Driver

[源码解析] 深度学习分布式训练框架 horovod (14) --- 弹性训练发现节点 & State

[源码解析] 深度学习分布式训练框架 horovod (15) --- 广播 & 通知

[源码解析] 深度学习分布式训练框架 horovod (16) --- 弹性训练之Worker生命周期

0x01总体思路

首先,我们需要注意的是:在某种程度上,容错和弹性调度互为因果

  • 容错的意思是,作业不受其中进程数量变化影响。
  • 弹性调度时,作业里的进程数量会随集群 workload 情况增减,所以作业必须是容错的,才能和调度系统配合,实现弹性调度。

其次,在源码的文档之中,有如下注释,我们可以看到容错具体思路。

  1. The reset process following a ``HorovodInternalError`` (failure) or ``HostsUpdatedInterrupt`` (add/remove request) is as follows:
  2. 1. Catch exception within the ``hvd.elastic.run`` decorator.
  3. 2. Restore last committed state if ``HorovodInternalError`` was raised.
  4. 3. Reinitialize Horovod context performing a new round of rendezvous.
  5. 4. Synchronize state among the workers by broadcasting from the new worker-0.
  6. 5. Resume training by executing the underlying training function.
  7. During rendezvous, older workers will take priority in being assigned worker-0 status to ensure that the state that is broadcast is up to date.

大致翻译如下:

对于出错状态下,在worker进程出现 HorvodInternalError 错误或者 HostsUpdateInterrupt 节点增删时,会捕获这两个错误,调用 reset 来进行容错处理:

  • hvd.elastic.run 装饰器捕获异常;
  • 如果是 HorovodInternalError,就恢复到最近一次提交(commit)的状态;
  • 重新初始化 Horovod context,然后driver 会根据当前正在运行的节点触发新的一轮的rendezvous,在rendezvous过程中,旧的worker会被优先被选举为新的rank-0,因为旧的worker才具有最新的状态;
  • 当新的通信域构造成功后,rank=0 的 worker 会将自身的模型(状态)广播给其他 worker;
  • 接着上次停止的迭代步数开始训练,继续跑下训练函数(train)中的代码;

我们具体来看看如何处理。

0x02 抛出异常

2.1 示例代码

我们首先回顾下用示例代码。

  1. import tensorflow as tf
  2. import horovod.tensorflow as hvd
  3. hvd.init()
  4. @tf.function
  5. def train_one_batch(data, target, allreduce=True):
  6. with tf.GradientTape() as tape:
  7. probs = model(data, training=True)
  8. loss = tf.losses.categorical_crossentropy(target, probs)
  9. if allreduce:
  10. tape = hvd.DistributedGradientTape(tape)
  11. gradients = tape.gradient(loss, model.trainable_variables)
  12. optimizer.apply_gradients(zip(gradients, model.trainable_variables))
  13. ....
  14. @hvd.elastic.run # 这里进行了包装,所以才能进行弹性训练
  15. def train(state):
  16. for state.epoch in range(state.epoch, epochs):
  17. for state.batch in range(state.batch, batches_per_epoch):
  18. data, target = get_random_batch()
  19. train_one_batch(data, target)
  20. if state.batch % batches_per_commit == 0:
  21. state.commit()
  22. state.batch = 0
  23. state = hvd.elastic.TensorFlowKerasState(model, optimizer, batch=0, epoch=0)
  24. state.register_reset_callbacks([on_state_reset])
  25. train(state)

最关键的就是用适配器 @hvd.elastic.run 包装了 train(state),所以我们顺着来看。

2.2 HorovodInternalError

从如下代码可知 hvd.elastic.run 就是 horovod/tensorflow/elastic.py 之中的 run 函数。

  1. import horovod.tensorflow as hvd
  2. @hvd.elastic.run

因此我们来到了 horovod/tensorflow/elastic.py。

func 就是用户训练函数,当运行用户训练函数出错时候,会根据捕获的异常信息来进行分析,如果是 ring allreduce 相关,就转为抛出异常 HorovodInternalError(e)

  1. def run(func):
  2. from tensorflow.python.framework.errors_impl import UnknownError
  3. def wrapper(state, *args, **kwargs):
  4. try:
  5. return func(state, *args, **kwargs)
  6. except UnknownError as e:
  7. # 判断是否是集合通信相关
  8. if 'HorovodAllreduce' in e.message or \
  9. 'HorovodAllgather' in e.message or \
  10. 'HorovodBroadcast' in e.message:
  11. raise HorovodInternalError(e)
  12. return run_fn(wrapper, _reset)

2.3 HostsUpdatedInterrupt

从前文我们知道:

当驱动进程通过节点发现脚本发现一个节点被标记为新增或者移除时,它将发送一个通知到所有workers,worker 根据通知来进行处理。

具体如下:

  • 驱动(后台发现)进程 获取 WorkerNotificationClient,然后调用 WorkerNotificationClient 来进行通知。就是利用 WorkerNotificationClient 发送 HostsUpdatedRequest。
  • WorkerNotificationService 继承了 network.BasicService,所以 WorkerNotificationClient 就是作为 WorkerNotificationService 的操作接口,从而给 WorkerNotificationService 发送 HostsUpdatedRequest。
  • WorkerNotificationService 会响应 HostsUpdatedRequest。调用 handle_hosts_updated 会逐一通知注册在WorkerNotificationManager 上的 listener(就是用户代码中的 State)。
  • 每一个 worker 有自己对应的 State,都位于 WorkerNotificationManager . _listeners
  • 每个worker收到通知之后,调用 _host_messages 会在state 之中注册 host 的变化,就是往其 _host_messages 之中放入"host 有变化" 的消息。
  • 在下一次 state.commit() 或者更轻量的 state.check_host_updates() 被调用时,state.check_host_updates 会从 _host_messages 中读取消息,积累更新,如方法中注释所述,会在每个 worker 之间同步状态,目的是让这些 worker 同时抛出 HostsUpdateInterrupt 异常。具体同步使用 _bcast_object(然后内部调用到了 MPI)。
  • state.check_host_updates() 会抛出 HostsUpdateInterrupt 异常。

具体代码如下:

在用户调用 commit 的时候,会调用 check_host_updates 检查更新。这里对用户代码是侵入了,用户使用到了框架的东西,虽然不知道 Driver,但是用到了框架的其他东西,比如 state。

  1. def commit(self):
  2. self.save()
  3. self.check_host_updates()

检查更新如下。

如果发现 host 有变化,就会产生一个 HostsUpdatedInterrupt 异常。

  1. def check_host_updates(self):
  2. # Iterate through the update messages sent from the server. If the update timestamp
  3. # is greater than the last update timestamp, then trigger a HostsUpdatedException.
  4. last_updated_timestamp = prev_timestamp = self._last_updated_timestamp
  5. all_update = HostUpdateResult.no_update
  6. while not self._host_messages.empty():
  7. timestamp, update = self._host_messages.get()
  8. if timestamp > last_updated_timestamp:
  9. last_updated_timestamp = timestamp
  10. all_update |= update
  11. prev_timestamp, self._last_updated_timestamp, all_update = \
  12. self._bcast_object((prev_timestamp, last_updated_timestamp, all_update))
  13. # At this point, updated state is globally consistent across all ranks.
  14. if self._last_updated_timestamp > prev_timestamp:
  15. # 抛出异常
  16. raise HostsUpdatedInterrupt(all_update == HostUpdateResult.removed)

2.4 总结

因此我们可以回答文初的两个问题:

  • 这些异常是 每个 worker 自动发出的吗?

    • 是的自动抛出的。
    • 当运行 用户训练函数出错时候,会根据捕获的异常信息来进行分析,如果是 ring allreduce 相关,就转为抛出异常 HorovodInternalError(e)。
    • 当如果发现 host 有变化,就会产生一个 HostsUpdatedInterrupt 异常。
  • 是 worker 们一起抛出异常吗?
    • 是一起抛出。
    • 如果训练出错,则都会抛出异常
    • 当驱动进程通过节点发现脚本发现一个节点被标记为新增或者移除时,它将发送一个通知到 所有workers,在下一次 state.commit() 或者更轻量的 state.check_host_updates() 被调用时,会一起抛出一个 HostsUpdateInterrupt 异常。

抛出异常的逻辑如下:

  1. +-----------------------------------------------------------------+
  2. | Worker |
  3. | |
  4. | HostsUpdatedInterrupt HorovodInternalError |
  5. | ^ ^ |
  6. | | | |
  7. | | +----------------------------------+ | |
  8. | | | train | | |
  9. | | | | | |
  10. | | | optimizer.apply_gradients +---------+ |
  11. | | | | |
  12. | +-------+ state.commit() |
  13. | | | |
  14. | +----------------------------------+ |
  15. | |
  16. | |
  17. +-----------------------------------------------------------------+

0x03 处理异常

3.1 总体逻辑

总体架构是 在 run_fn 之中。

回忆一下 run_fn 是从哪里来调用的。原来是在 run 之中,就是运行 wrapper。而 wrapper 本身是对用户训练函数的包装

  1. def run(func):
  2. from tensorflow.python.framework.errors_impl import UnknownError
  3. def wrapper(state, *args, **kwargs):
  4. try:
  5. return func(state, *args, **kwargs)
  6. except UnknownError as e:
  7. if 'HorovodAllreduce' in e.message or \
  8. 'HorovodAllgather' in e.message or \
  9. 'HorovodBroadcast' in e.message:
  10. raise HorovodInternalError(e)
  11. return run_fn(wrapper, _reset)

大概逻辑如图:

  1. +----------------------------------------------------------------------------+
  2. | Worker |
  3. | |
  4. | +----------------------------------------------------------------------+ |
  5. | | run_fn | |
  6. | | | |
  7. | | | |
  8. | | | |
  9. | | | |
  10. | | | |
  11. | | | |
  12. | | | |
  13. | | HostsUpdatedInterrupt HorovodInternalError | |
  14. | | ^ ^ | |
  15. | | | | | |
  16. | | | +----------------------------------+ | | |
  17. | | | | train | | | |
  18. | | | | | | | |
  19. | | | | optimizer.apply_gradients +---------+ | |
  20. | | | | | | |
  21. | | +-------+ state.commit() | |
  22. | | | | | |
  23. | | +----------------------------------+ | |
  24. | | | |
  25. | | | |
  26. | | | |
  27. | +----------------------------------------------------------------------+ |
  28. +----------------------------------------------------------------------------+

run_fn逻辑如下:

  • 当 HorovodInternalError 产生,就会调用 state.restore() 来恢复;
  • 当 HostsUpdatedInterrupt 被捕获,会设置 skip_sync;
  • 调用 reset(),state.on_reset() 进行重置;
  • 下次循环,会根据 skip_sync 决定是否执行 state.sync();

具体代码如下:

  1. def run_fn(func, reset):
  2. @functools.wraps(func)
  3. def wrapper(state, *args, **kwargs):
  4. notification_manager.init()
  5. notification_manager.register_listener(state)
  6. skip_sync = False
  7. try:
  8. while True:
  9. if not skip_sync:
  10. state.sync()
  11. try:
  12. return func(state, *args, **kwargs)
  13. except HorovodInternalError:
  14. state.restore()
  15. skip_sync = False
  16. except HostsUpdatedInterrupt as e:
  17. skip_sync = e.skip_sync
  18. reset()
  19. state.on_reset()
  20. finally:
  21. notification_manager.remove_listener(state)
  22. return wrapper

所以我们拓展逻辑如下:

  1. +------------------------------------------------------------------------------+
  2. | Worker |
  3. | |
  4. | +------------------------------------------------------------------------+ |
  5. | | run_fn | |
  6. | | +----------------------------------+ | |
  7. | | | while True: | | |
  8. | | | | | |
  9. | | v | | |
  10. | | | | |
  11. | | state.sync() | | |
  12. | | + | | |
  13. | | | | | |
  14. | | | | | |
  15. | | v | | |
  16. | | +------------------+---------------+ | | |
  17. | | | train | | | |
  18. | | | | | | |
  19. | | | optimizer.apply_gradients +---------+ | | |
  20. | | | | | | | |
  21. | | +-------+ state.commit() | | | |
  22. | | | | | | | | |
  23. | | | +----------------------------------+ | | | |
  24. | | | | | | |
  25. | | v v | | |
  26. | | HostsUpdatedInterrupt HorovodInternalError | | |
  27. | | + | | |
  28. | | + | | | |
  29. | | | | | | |
  30. | | | v | | |
  31. | | | state.restore() | | |
  32. | | | + | | |
  33. | | | | | | |
  34. | | +------------------+ <------------------+ | | |
  35. | | | | | | |
  36. | | | | | | |
  37. | | v v | | |
  38. | | reset() | | |
  39. | | | | |
  40. | | state.on_reset() | | |
  41. | | | | |
  42. | | + | | |
  43. | | | | | |
  44. | | +-----------------------------------> | |
  45. | | | |
  46. | +------------------------------------------------------------------------+ |
  47. | |
  48. +------------------------------------------------------------------------------+

3.2 恢复

state.restore() 会进行恢复。

在 TensorFlowKerasState 之中,实现了 restore。

  1. def restore(self):
  2. self._load_model()
  3. super(TensorFlowKerasState, self).restore()

具体 restore 就是重新加载模型,具体加载就是利用 TensorFlowKerasState 的 model, optimizer 这两个成员变量。

  1. def _load_model(self):
  2. if _executing_eagerly():
  3. for var, saved_var in zip(self.model.variables, self._saved_model_state):
  4. var.assign(saved_var)
  5. for var, saved_var in zip(self.optimizer.variables(), self._saved_optimizer_state):
  6. var.assign(saved_var)
  7. else:
  8. self.model.set_weights(self._saved_model_state)
  9. self.optimizer.set_weights(self._saved_optimizer_state)

我们拓展如下:

  1. +---------------------------------------------------------------------------------------------------------+
  2. | Worker |
  3. | |
  4. | +----------------------------------------------------------------------------------------------------+ |
  5. | | run_fn | |
  6. | | +---------------------------------------------------------------+ | |
  7. | | | while True: | | |
  8. | | | | | |
  9. | | v | | |
  10. | | | | |
  11. | | state.sync() | | |
  12. | | + | | |
  13. | | | | | |
  14. | | | | | |
  15. | | v | | |
  16. | | +------------------+---------------+ | | |
  17. | | | train | | | |
  18. | | | | | | |
  19. | | | optimizer.apply_gradients +---------+ | | |
  20. | | | | | | | |
  21. | | +-------+ state.commit() | | | |
  22. | | | | | | | | |
  23. | | | +----------------------------------+ | | | |
  24. | | | | | | |
  25. | | v v | | |
  26. | | HostsUpdatedInterrupt HorovodInternalError | | |
  27. | | + | | |
  28. | | + | | | |
  29. | | | | | | |
  30. | | | v +-------------------------+ | | |
  31. | | | state.restore() +---> | _load_model | | | |
  32. | | | + | | | | |
  33. | | | | | model.set_weights | | | |
  34. | | +------------------+ <------------------+ | optimizer.set_weights | | | |
  35. | | | | | var.assign(saved_var) | | | |
  36. | | | | | | | | |
  37. | | v v +-------------------------+ | | |
  38. | | reset() | | |
  39. | | | | |
  40. | | state.on_reset() | | |
  41. | | | | |
  42. | | + | | |
  43. | | | | | |
  44. | | +----------------------------------------------------------------^ | |
  45. | | | |
  46. | +----------------------------------------------------------------------------------------------------+ |
  47. | |
  48. +---------------------------------------------------------------------------------------------------------+

手机如下:

3.3 重置

以下代码会进行重置操作。

  1. reset()
  2. state.on_reset()

3.3.1 reset

具体 reset 函数是:

  1. def _reset():
  2. shutdown()
  3. init()

3.3.2 _HorovodBasics

具体使用了 _HorovodBasics 这里的函数。

  1. _basics = _HorovodBasics(__file__, 'mpi_lib')
  2. init = _basics.init
  3. shutdown = _basics.shutdown

具体如下,就是重新建立 MPI 相关 context

  1. def init(self, comm=None):
  2. if comm is None:
  3. comm = []
  4. atexit.register(self.shutdown)
  5. if not isinstance(comm, list):
  6. mpi_built = self.MPI_LIB_CTYPES.horovod_mpi_built()
  7. from mpi4py import MPI
  8. if MPI._sizeof(MPI.Comm) == ctypes.sizeof(ctypes.c_int):
  9. MPI_Comm = ctypes.c_int
  10. else:
  11. MPI_Comm = ctypes.c_void_p
  12. self.MPI_LIB_CTYPES.horovod_init_comm.argtypes = [MPI_Comm]
  13. comm_obj = MPI_Comm.from_address(MPI._addressof(comm))
  14. self.MPI_LIB_CTYPES.horovod_init_comm(comm_obj)
  15. else:
  16. comm_size = len(comm)
  17. self.MPI_LIB_CTYPES.horovod_init(
  18. (ctypes.c_int * comm_size)(*comm), ctypes.c_int(comm_size))
  19. def shutdown(self):
  20. self.MPI_LIB_CTYPES.horovod_shutdown()

3.3.3 on_reset

是执行用户设置的 reset callback。

  1. def on_reset(self):
  2. self._host_messages = queue.Queue()
  3. self.reset()
  4. for callback in self._reset_callbacks:
  5. callback()

比如用户设置如下callback:

  1. def on_state_reset():
  2. optimizer.lr.assign(lr * hvd.size())

此时逻辑如下:

  1. +-------------------------------------------------------------------------------------------------------------+
  2. | Worker |
  3. | |
  4. | +--------------------------------------------------------------------------------------------------------+ |
  5. | | run_fn | |
  6. | | +-----------------------------------------------------------------+ | |
  7. | | | while True: | | |
  8. | | | | | |
  9. | | v | | |
  10. | | | | |
  11. | | state.sync() | | |
  12. | | + | | |
  13. | | | | | |
  14. | | | | | |
  15. | | v | | |
  16. | | +------------------+---------------+ | | |
  17. | | | train | | | |
  18. | | | | | | |
  19. | | | optimizer.apply_gradients +---------+ | | |
  20. | | | | | | | |
  21. | | +-------+ state.commit() | | | |
  22. | | | | | | | | |
  23. | | | +----------------------------------+ | | | |
  24. | | | | | | |
  25. | | v v | | |
  26. | | HostsUpdatedInterrupt HorovodInternalError +-------------------------+ | | |
  27. | | + | _load_model | | | |
  28. | | + | | | | | |
  29. | | | | | model.set_weights | | | |
  30. | | | v | optimizer.set_weights | | | |
  31. | | | state.restore() +---> | var.assign(saved_var) | | | |
  32. | | | + | | | | |
  33. | | | | +-------------------------+ | | |
  34. | | +------------------+ <------------------+ | | |
  35. | | | | +-------------------------+ | | |
  36. | | | | | _HorovodBasics | | | |
  37. | | v v | | | | |
  38. | | reset() +-----------------------------> | | | | |
  39. | | +---------------+ | horovod_init | | | |
  40. | | | user callback +<----+ state.on_reset() | | | | |
  41. | | +---------------+ | horovod_init_comm | | | |
  42. | | + | | | | |
  43. | | | +-------------------------+ | | |
  44. | | +------------------------------------------------------------------^ | |
  45. | | | |
  46. | +--------------------------------------------------------------------------------------------------------+ |
  47. | |
  48. +-------------------------------------------------------------------------------------------------------------+

手机如下:

3.3.4 sync

当重置时候,用户也会进行必要的同步,具体是广播变量 和 存模型 两步。

  1. def sync(self):
  2. if self.session is not None:
  3. self.session.run(self._bcast_op)
  4. self._save_model()
  5. super(TensorFlowState, self).sync()
3.3.4.1 广播

广播函数在之前初始化时候有设置

  1. self._bcast_op = broadcast_variables(self.variables, root_rank=0)

因此,就是 当新的通信域构造成功后,rank=0 的 worker 会将自身的模型广播给其他 worker

3.3.4.2 存模型

存模型就是调用 _eval_fn 来把模型变量转存到内存之中。

  1. def _save_model(self):
  2. self._values = [self._eval_fn(var) for var in self.variables]

_eval_fn 在 之前初始化时候有设置

  1. self._eval_fn = self._to_numpy if _executing_eagerly() else self._eval_var

具体函数是:

  1. def _eval_var(self, var):
  2. return var.eval(self.session)
  3. def _to_numpy(self, var):
  4. return var.numpy()

所以我们的逻辑拓展如下:

  1. +-------------------------------------------------------------------------------------------------------------------+
  2. | Worker |
  3. | |
  4. | +-------------------------------------------------------------------------------------------------------------+ |
  5. | | run_fn | |
  6. | | +----------------------------------------------------------------------+ | |
  7. | | | while True: | | |
  8. | | | | | |
  9. | | v | | |
  10. | | +-------------------------------------------------+ | | |
  11. | | state.sync() +--------> |broadcast_variables(self.variables, root_rank=0) | | | |
  12. | | + | | | | |
  13. | | | | _save_model | | | |
  14. | | | | | | | |
  15. | | v +-------------------------------------------------+ | | |
  16. | | +------------------+---------------+ | | |
  17. | | | train | | | |
  18. | | | | | | |
  19. | | | optimizer.apply_gradients +---------+ | | |
  20. | | | | | | | |
  21. | | +-------+ state.commit() | | | |
  22. | | | | | | | | |
  23. | | | +----------------------------------+ | | | |
  24. | | | | | | |
  25. | | v v | | |
  26. | | HostsUpdatedInterrupt HorovodInternalError +-------------------------+ | | |
  27. | | + | _load_model | | | |
  28. | | + | | | | | |
  29. | | | | | model.set_weights | | | |
  30. | | | v | optimizer.set_weights | | | |
  31. | | | state.restore() +---> | var.assign(saved_var) | | | |
  32. | | | + | | | | |
  33. | | | | +-------------------------+ | | |
  34. | | +------------------+ <------------------+ | | |
  35. | | | | +-------------------------+ | | |
  36. | | | | | _HorovodBasics | | | |
  37. | | v v | | | | |
  38. | | reset() +-----------------------------> | | | | |
  39. | | +---------------+ | horovod_init | | | |
  40. | | | user callback +<----+ state.on_reset() | | | | |
  41. | | +---------------+ | horovod_init_comm | | | |
  42. | | + | | | | |
  43. | | | +-------------------------+ | | |
  44. | | +-----------------------------------------------------------------------^ | |
  45. | | | |
  46. | +-------------------------------------------------------------------------------------------------------------+ |
  47. | |
  48. +-------------------------------------------------------------------------------------------------------------------+

手机如下:

至此,弹性训练部分分析结束。下面二~三篇文章将为大家介绍K8S相关。

0xFF 参考

ElasticDL调用 Horovod 在Kubernetes上实现弹性 AllReduce(一)

kubernetes 培训_在Kubernetes上使用horovod进行分布式深度学习培训

在 Kubernetes 上弹性深度学习训练利器 -- Elastic Training Operator

ElasticHorovod - 弹性、容错的分布式训练 (尝鲜版)

Horovod 弹性训练

Kubernetes-native 弹性分布式深度学习系统

[源码解析] 深度学习分布式训练框架 horovod (17) --- 弹性训练之容错的更多相关文章

  1. [源码解析] 深度学习分布式训练框架 Horovod (1) --- 基础知识

    [源码解析] 深度学习分布式训练框架 Horovod --- (1) 基础知识 目录 [源码解析] 深度学习分布式训练框架 Horovod --- (1) 基础知识 0x00 摘要 0x01 分布式并 ...

  2. [源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入

    [源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入 目录 [源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入 0x00 摘要 0 ...

  3. [源码解析] 深度学习分布式训练框架 horovod (4) --- 网络基础 & Driver

    [源码解析] 深度学习分布式训练框架 horovod (4) --- 网络基础 & Driver 目录 [源码解析] 深度学习分布式训练框架 horovod (4) --- 网络基础 & ...

  4. [源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun背后做了什么

    [源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun背后做了什么 目录 [源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun ...

  5. [源码解析] 深度学习分布式训练框架 horovod (5) --- 融合框架

    [源码解析] 深度学习分布式训练框架 horovod (5) --- 融合框架 目录 [源码解析] 深度学习分布式训练框架 horovod (5) --- 融合框架 0x00 摘要 0x01 架构图 ...

  6. [源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构

    [源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构 目录 [源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构 0x00 摘要 0x01 ...

  7. [源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer

    [源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer 目录 [源码解析] 深度学习分布式训练框架 horovod (7) --- Distri ...

  8. [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark

    [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark 目录 [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark 0x00 摘要 0 ...

  9. [源码解析] 深度学习分布式训练框架 horovod (9) --- 启动 on spark

    [源码解析] 深度学习分布式训练框架 horovod (9) --- 启动 on spark 目录 [源码解析] 深度学习分布式训练框架 horovod (9) --- 启动 on spark 0x0 ...

  10. [源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark

    [源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark 目录 [源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark ...

随机推荐

  1. 使用ubuntu charmed kubernetes 部署一套生产环境的集群

    官方文档: https://ubuntu.com/kubernetes/docs 搭建一个基本的集群 集群ip规划 hostname ip ubuntu-1 10.0.0.10 juju-contro ...

  2. AI+IoT+电池应用

    AI+IoT+电池应用 AIoT电池 突破你的想象 将行业领先的电池电化学技术与前沿的能源物联网最佳实践相结合,利用智能物联技术开展电池全生命周期的管理优化和交叉领域的协同应用,解锁动力电池全生命周期 ...

  3. Python“九九乘法表”

    用Python语言编程,使用双重循环语句输出"九九乘法表". for i in range(1, 10): # 控制行 for j in range(1, i+1): # 控制列 ...

  4. 像Swing这种已经不太用的技术,大学还在教,到底要不要学?

    一直以来,写日常问题.前沿技术和架构思考类的文章比较多,今天为什么突然来说说Swing这个陈年老技术呢? 因为在CSDN上看到了这样的一篇文章: 可以看到作者对于学Swing还是挺愤怒的,不过确实Sw ...

  5. C#搞跨平台桌面UI,分别实现Windows,Mac,Linux屏幕截图

    搞跨平台IM,截图功能少不了. Windows 创建GDI的兼容位图,把桌面的图像通过BitBlt拷贝到兼容位图里,通过兼容位图的数据指针创建Bitmap对象,由于兼容位图的内存是非托管的,Bitma ...

  6. PTA题目集总结

    PTA题目集1-3总结 一:前言 我认为题目集一的有八个题目,题量可能稍微有点多,但是题型较为简单,基本为入门题:题集二有五道题,题量适度,难度也适中:题集三虽然只有三道题,但是难度却骤然提升,前两题 ...

  7. 干货 | LuatOS BSP移植教程,简单到复制粘贴!!!

    LuatOS本着自身的开源特性,可以很轻松的嵌入到很多微处理器和微控制器.今天简要讲下如何移植这套系统,上手比较简单,看完基本就会了. 要想做移植,就要先了解需要移植芯片的SDK,LuatOS依赖于F ...

  8. Mysql优化(出自官方文档) - 第二篇

    Mysql优化(出自官方文档) - 第二篇 目录 Mysql优化(出自官方文档) - 第二篇 1 关于Nested Loop Join的相关知识 1.1 相关概念和算法 1.2 一些优化 1 关于Ne ...

  9. 深入理解 sync.Once 与 sync.Pool

    深入理解 sync.Once 与 sync.Pool sync.Once 代表在这个对象下在这个示例下多次执行能保证只会执行一次操作. var once sync.Once for i:=0; i & ...

  10. 『无为则无心』Python基础 — 6、Python的注释

    目录 1.注释的作用 2.注释的分类 单行注释 多行注释 3.注释的注意事项 4.什么时候需要使用注释 5.总结 提示:完成了前面的准备工作,之后的文章开始介绍Python的基本语法了. Python ...