2.1 如何运行
官方给出的 Hovorod 运行范例之一如下:
horovodrun -np 2 -H localhost:4 --gloo python /horovod/examples/tensorflow2/tensorflow2_mnist.py
这里 -np 指的是进程的数量,localhost:4表示localhost节点上4个GPU。
注意,如果虚拟机只有一个核。想要强行地达到并行的效果,可以使用 -np参数,它会自动帮你把一个核心切成多份处理器,每一个分布式处理就是一个slot。
因此,我们可以从 horovodrun 这个命令入手看看。
2.2 horovodrun
入口文件可以从 setup.py 看到,其就被映射成 horovod.runner.launch:run_commandline。
entry_points={
‘console_scripts’: [
‘horovodrun = horovod.runner.launch:run_commandline’
]
}
所以我们看看 run_commandline
2.3 run_commandline
该命令位于:horovod-master/horovod/runner/launch.py,我们摘录重要部分。
def run_commandline():
args = parse_args()
_run(args)
于是进入到 _run 函数。可以看到,Horovod 会依据是否是弹性训练来选择不同的路径。我们在此系列中,会首先分析 非弹性训练 _run_static。
def _run(args):
# if hosts are not specified, either parse from hostfile, or default as
# localhost
if not args.hosts and not args.host_discovery_script:
if args.hostfile:
args.hosts = hosts.parse_host_files(args.hostfile)
else:
# Set hosts to localhost if not specified
args.hosts = ‘localhost:{np}’.format(np=args.np)
# Convert nics into set
args.nics = set(args.nics.split(',')) if args.nics else None
if _is_elastic(args):
return _run_elastic(args)
else:
return _run_static(args) # 我们先看这里
2.4 非弹性训练 _run_static
在 _run_static 之中做了如下操作:
首先解析各种参数,得到 settings;
会调用 driver_service.get_common_interfaces 获取网卡以及其他host的信息,依据这些信息会进行slot分配,这部分很复杂,具体我们会有专文讲解(下一篇)。
这里有一个问题:为什么要得到 host, slot, rank 之间的关系信息?由于工程上的考虑,底层 C++ 世界中对于 rank 的角色做了区分:rank 0 是 master,rank n 是 worker,所以这些信息需要决定并且传递给 C++世界;
会根据是否在参数中传递运行函数来决定采取何种路径,一般默认没有运行参数,所以会执行_launch_job 来启动训练 job;
具体代码如下:
def _run_static(args):
settings = hvd_settings.Settings(verbose=2 if args.verbose else 0,
ssh_port=args.ssh_port,
ssh_identity_file=args.ssh_identity_file,
extra_mpi_args=args.mpi_args,
tcp_flag=args.tcp_flag,
binding_args=args.binding_args,
key=secret.make_secret_key(),
start_timeout=tmout,
num_proc=args.np,
hosts=args.hosts,
output_filename=args.output_filename,
run_func_mode=args.run_func is not None,
nics=args.nics,...)
# 首先解析各种参数,得到 settings
fn_cache = None
if not args.disable_cache:
params = ''
if args.np:
params += str(args.np) + ' '
if args.hosts:
params += str(args.hosts) + ' '
if args.ssh_port:
params += str(args.ssh_port)
if args.ssh_identity_file:
params += args.ssh_identity_file
parameters_hash = hashlib.md5(params.encode('utf-8')).hexdigest()
fn_cache = cache.Cache(CACHE_FOLDER, CACHE_STALENESS_THRESHOLD_MINUTES,
parameters_hash)
# 获取网卡以及其他host的信息,依据这些信息会进行slot分配
all_host_names, _ = hosts.parse_hosts_and_slots(args.hosts)
remote_host_names = network.filter_local_addresses(all_host_names)
nics = driver_service.get_common_interfaces(settings, all_host_names,
remote_host_names, fn_cache)
if args.run_func:
# get the driver IPv4 address
driver_ip = network.get_driver_ip(nics)
run_func_server = KVStoreServer(verbose=settings.verbose) # 启动内部KV服务器
run_func_server_port = run_func_server.start_server()
put_data_into_kvstore(driver_ip, run_func_server_port,
'runfunc', 'func', args.run_func) # 把'func', args.run_func存储成KV
command = [sys.executable, '-m', 'horovod.runner.run_task', str(driver_ip), str(run_func_server_port)]
try:
_launch_job(args, settings, nics, command)
results = [None] * args.np
for i in range(args.np):
results[i] = read_data_from_kvstore(driver_ip, run_func_server_port,'runfunc_result', str(i))
return results
finally:
run_func_server.shutdown_server()
else:
command = args.command
_launch_job(args, settings, nics, command) # 我们重点讲解这里
return None
目前逻辑如下:
+-----------+
|horovodrun |
+-----+-----+
|
|
v
+--------+--------+
| run_commandline |
+----+------+-----+
| |
+---------+ +--------+
| |
| |
v v
±----±-------+ ±—±-------+
| _run_elastic | | _run_static |
| | | |
±-------------+ ±------------+
至此,我们已经分析完成 horovod 的入口,下面会分析具体如何启动 Job。
0x03 运行训练 Job
3.1 _launch_job
_launch_job 会根据配置或者安装情况来进行具体调用。我们看到有三种可能:gloo, mpi, js。
jsrun的资料很难找,所以我们重点看看 gloo, mpi 这两种。
def _launch_job(args, settings, nics, command):
env = os.environ.copy()
config_parser.set_env_from_args(env, args)
def gloo_run_fn():
driver_ip = network.get_driver_ip(nics)
gloo_run(settings, nics, env, driver_ip, command)
def mpi_run_fn():
mpi_run(settings, nics, env, command)
def js_run_fn():
js_run(settings, nics, env, command)
run_controller(args.use_gloo, gloo_run_fn,
args.use_mpi, mpi_run_fn,
args.use_jsrun, js_run_fn,
args.verbose)
3.2 run_controller
run_controller 依然是一个中介函数,具体导入 gloo 或者 mpi。
def run_controller(use_gloo, gloo_run, use_mpi, mpi_run, use_jsrun, js_run, verbosity):
if use_gloo:
gloo_run()
elif use_mpi:
mpi_run()
elif use_jsrun:
js_run()
else:
if mpi_built(verbose=verbose):
if lsf.LSFUtils.using_lsf() and is_jsrun_installed():
js_run()
else:
mpi_run()
elif gloo_built(verbose=verbose):
gloo_run()
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com